ElggEntity::save()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 9.3088

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 0
dl 0
loc 20
ccs 4
cts 13
cp 0.3076
crap 9.3088
rs 9.6
c 0
b 0
f 0
1
<?php
2
/**
3
 * The parent class for all Elgg Entities.
4
 *
5
 * An \ElggEntity is one of the basic data models in Elgg.  It is the primary
6
 * means of storing and retrieving data from the database.  An \ElggEntity
7
 * represents one row of the entities table.
8
 *
9
 * The \ElggEntity class handles CRUD operations for the entities table.
10
 * \ElggEntity should always be extended by another class to handle CRUD
11
 * operations on the type-specific table.
12
 *
13
 * \ElggEntity uses magic methods for get and set, so any property that isn't
14
 * declared will be assumed to be metadata and written to the database
15
 * as metadata on the object.  All children classes must declare which
16
 * properties are columns of the type table or they will be assumed
17
 * to be metadata.  See \ElggObject::initializeAttributes() for examples.
18
 *
19
 * Core supports 4 types of entities: \ElggObject, \ElggUser, \ElggGroup, and
20
 * \ElggSite.
21
 *
22
 * @tip Plugin authors will want to extend the \ElggObject class, not this class.
23
 *
24
 * @package    Elgg.Core
25
 * @subpackage DataModel.Entities
26
 *
27
 * @property       string $type           object, user, group, or site (read-only after save)
28
 * @property-write string $subtype        Further clarifies the nature of the entity (this should not be read)
29
 * @property       int    $guid           The unique identifier for this entity (read only)
30
 * @property       int    $owner_guid     The GUID of the owner of this entity (usually the creator)
31
 * @property       int    $container_guid The GUID of the entity containing this entity
32
 * @property       int    $site_guid      The GUID of the website this entity is associated with
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       int    $time_updated   A UNIX timestamp of when the entity was last updated (automatically updated on save)
36
 * @property       string $enabled        Is this entity enabled ('yes' or 'no')
37
 *
38
 * Metadata (the above are attributes)
39
 * @property       string $location       A location of the entity
40
 */
41
abstract class ElggEntity extends \ElggData implements
42
	Notable,   // Calendar interface (deprecated 1.9)
0 ignored issues
show
Deprecated Code introduced by
The interface Notable has been deprecated with message: 1.9

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
43
	Locatable, // Geocoding interface
44
	Importable // Allow import of data (deprecated 1.9)
0 ignored issues
show
Deprecated Code introduced by
The interface Importable has been deprecated with message: 1.9

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
45
{
46
47
	/**
48
	 * If set, overrides the value of getURL()
49
	 */
50
	protected $url_override;
51
52
	/**
53
	 * Icon override, overrides the value of getIcon().
54
	 */
55
	protected $icon_override;
56
57
	/**
58
	 * Holds metadata until entity is saved.  Once the entity is saved,
59
	 * metadata are written immediately to the database.
60
	 */
61
	protected $temp_metadata = array();
62
63
	/**
64
	 * Holds annotations until entity is saved.  Once the entity is saved,
65
	 * annotations are written immediately to the database.
66
	 */
67
	protected $temp_annotations = array();
68
69
	/**
70
	 * Holds private settings until entity is saved. Once the entity is saved,
71
	 * private settings are written immediately to the database.
72
	 */
73
	protected $temp_private_settings = array();
74
75
	/**
76
	 * Volatile data structure for this object, allows for storage of data
77
	 * in-memory that isn't sync'd back to the metadata table.
78
	 */
79
	protected $volatile = array();
80
	
81
	/**
82
	 * Tells how many tables are going to need to be searched in order to fully populate this object
83
	 *
84
	 * @var int
85
	 */
86
	protected $tables_split;
87
	
88
	/**
89
	 * Tells how many tables describing object have been loaded thus far
90
	 *
91
	 * @var int
92
	 */
93
	protected $tables_loaded;
94
	
95
	/**
96
	 * Initialize the attributes array.
97
	 *
98
	 * This is vital to distinguish between metadata and base parameters.
99
	 *
100
	 * @return void
101
	 */
102 32
	protected function initializeAttributes() {
103 32
		parent::initializeAttributes();
104
105 32
		$this->attributes['guid'] = null;
106 32
		$this->attributes['type'] = null;
107 32
		$this->attributes['subtype'] = null;
108
109 32
		$this->attributes['owner_guid'] = _elgg_services()->session->getLoggedInUserGuid();
110 32
		$this->attributes['container_guid'] = _elgg_services()->session->getLoggedInUserGuid();
111
112 32
		$this->attributes['site_guid'] = null;
113 32
		$this->attributes['access_id'] = ACCESS_PRIVATE;
114 32
		$this->attributes['time_updated'] = null;
115 32
		$this->attributes['last_action'] = null;
116 32
		$this->attributes['enabled'] = "yes";
117
118
		// There now follows a bit of a hack
119
		/* Problem: To speed things up, some objects are split over several tables,
120
		 * this means that it requires n number of database reads to fully populate
121
		 * an entity. This causes problems for caching and create events
122
		 * since it is not possible to tell whether a subclassed entity is complete.
123
		 *
124
		 * Solution: We have two counters, one 'tables_split' which tells whatever is
125
		 * interested how many tables are going to need to be searched in order to fully
126
		 * populate this object, and 'tables_loaded' which is how many have been
127
		 * loaded thus far.
128
		 *
129
		 * If the two are the same then this object is complete.
130
		 *
131
		 * Use: isFullyLoaded() to check
132
		 */
133 32
		$this->tables_split = 1;
134 32
		$this->tables_loaded = 0;
135 32
	}
136
137
	/**
138
	 * Clone an entity
139
	 *
140
	 * Resets the guid so that the entity can be saved as a distinct entity from
141
	 * the original. Creation time will be set when this new entity is saved.
142
	 * The owner and container guids come from the original entity. The clone
143
	 * method copies metadata but does not copy annotations or private settings.
144
	 *
145
	 * @note metadata will have its owner and access id set when the entity is saved
146
	 * and it will be the same as that of the entity.
147
	 *
148
	 * @return void
149
	 */
150
	public function __clone() {
151
		$orig_entity = get_entity($this->guid);
152
		if (!$orig_entity) {
153
			_elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
154
			return;
155
		}
156
157
		$metadata_array = elgg_get_metadata(array(
158
			'guid' => $this->guid,
159
			'limit' => 0
160
		));
161
162
		$this->attributes['guid'] = "";
163
164
		$this->attributes['subtype'] = $orig_entity->getSubtype();
165
166
		// copy metadata over to new entity - slightly convoluted due to
167
		// handling of metadata arrays
168
		if (is_array($metadata_array)) {
169
			// create list of metadata names
170
			$metadata_names = array();
171
			foreach ($metadata_array as $metadata) {
172
				$metadata_names[] = $metadata['name'];
173
			}
174
			// arrays are stored with multiple enties per name
175
			$metadata_names = array_unique($metadata_names);
176
177
			// move the metadata over
178
			foreach ($metadata_names as $name) {
179
				$this->__set($name, $orig_entity->$name);
180
			}
181
		}
182
	}
183
184
	/**
185
	 * Set an attribute or metadata value for this entity
186
	 *
187
	 * Anything that is not an attribute is saved as metadata.
188
	 *
189
	 * @warning Metadata set this way will inherit the entity's owner and
190
	 * access ID. If you want more control over metadata, use \ElggEntity::setMetadata()
191
	 *
192
	 * @param string $name  Name of the attribute or metadata
193
	 * @param mixed  $value The value to be set
194
	 * @return void
195
	 * @see \ElggEntity::setMetadata()
196
	 */
197 15
	public function __set($name, $value) {
198 15
		if ($this->$name === $value) {
199
			// quick return if value is not changing
200
			return;
201
		}
202 15
		if (array_key_exists($name, $this->attributes)) {
203
			// Certain properties should not be manually changed!
204
			switch ($name) {
205 13
				case 'guid':
206 13
				case 'time_updated':
207 13
				case 'last_action':
208 1
					return;
209
					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...
210 12
				case 'access_id':
211 12
				case 'owner_guid':
212 12 View Code Duplication
				case 'container_guid':
213 3
					if ($value !== null) {
214 3
						$this->attributes[$name] = (int)$value;
215 3
					} else {
216
						$this->attributes[$name] = null;
217
					}
218 3
					break;
219 10
				default:
220 10
					$this->attributes[$name] = $value;
221 10
					break;
222 10
			}
223 12
		} else {
224 2
			$this->setMetadata($name, $value);
225
		}
226 14
	}
227
228
	/**
229
	 * Sets the value of an attribute or metadata
230
	 *
231
	 * @param string $name  Name
232
	 * @param mixed  $value Value
233
	 *
234
	 * @return bool
235
	 * @deprecated 1.9
236
	 */
237
	public function set($name, $value) {
238
		elgg_deprecated_notice("Use -> instead of set()", 1.9);
239
		$this->__set($name, $value);
240
241
		return true;
242
	}
243
244
	/**
245
	 * Get an attribute or metadata value
246
	 *
247
	 * If the name matches an attribute, the attribute is returned. If metadata
248
	 * does not exist with that name, a null is returned.
249
	 *
250
	 * This only returns an array if there are multiple values for a particular
251
	 * $name key.
252
	 *
253
	 * @param string $name Name of the attribute or metadata
254
	 * @return mixed
255
	 */
256 28
	public function __get($name) {
257 28
		if (array_key_exists($name, $this->attributes)) {
258 28
			if ($name === 'subtype' && $this->attributes['guid']) {
259
				// note: only show deprecation notice if user reads ->subtype after save/load
260
				elgg_deprecated_notice("Use getSubtype()", 1.9);
261
			}
262 28
			return $this->attributes[$name];
263
		}
264
265 3
		return $this->getMetadata($name);
266
	}
267
268
	/**
269
	 * Return the value of an attribute or metadata
270
	 *
271
	 * @param string $name Name
272
	 * @return mixed Returns the value of a given value, or null.
273
	 * @deprecated 1.9
274
	 */
275
	public function get($name) {
276
		elgg_deprecated_notice("Use -> instead of get()", 1.9);
277
		return $this->__get($name);
278
	}
279
280
	/**
281
	 * Get the entity's display name
282
	 *
283
	 * @return string The title or name of this entity.
284
	 */
285
	abstract public function getDisplayName();
286
287
	/**
288
	 * Sets the title or name of this entity.
289
	 *
290
	 * @param string $displayName The title or name of this entity.
291
	 * @return void
292
	 */
293
	abstract public function setDisplayName($displayName);
294
295
	/**
296
	 * Return the value of a piece of metadata.
297
	 *
298
	 * @param string $name Name
299
	 *
300
	 * @return mixed The value, or null if not found.
301
	 */
302 3
	public function getMetadata($name) {
303 3
		$guid = $this->getGUID();
304
305 3
		if (!$guid) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $guid of type integer|null 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...
306 3
			if (isset($this->temp_metadata[$name])) {
307
				// md is returned as an array only if more than 1 entry
308 2
				if (count($this->temp_metadata[$name]) == 1) {
309 2
					return $this->temp_metadata[$name][0];
310
				} else {
311
					return $this->temp_metadata[$name];
312
				}
313
			} else {
314 3
				return null;
315
			}
316
		}
317
318
		// upon first cache miss, just load/cache all the metadata and retry.
319
		// 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(array($guid));
325
			// in case ignore_access was on, we have to check again...
326
			if ($cache->isLoaded($guid)) {
327
				return $cache->getSingle($guid, $name);
328
			}
329
		}
330
331
		$md = elgg_get_metadata(array(
332
			'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 1
	public function __unset($name) {
362 1
		if (array_key_exists($name, $this->attributes)) {
363 1
			$this->attributes[$name] = "";
364 1
		} else {
365
			$this->deleteMetadata($name);
366
		}
367 1
	}
368
369
	/**
370
	 * Set metadata on this entity.
371
	 *
372
	 * 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
	 * @param int    $access_id  Who can read the metadata relative to the owner.
384
	 *                           Default is the access level of the entity.
385
	 *
386
	 * @return bool
387
	 * @throws InvalidArgumentException
388
	 */
389 2
	public function setMetadata($name, $value, $value_type = '', $multiple = false, $owner_guid = 0, $access_id = null) {
390
391
		// normalize value to an array that we will loop over
392
		// remove indexes if value already an array.
393 2
		if (is_array($value)) {
394
			$value = array_values($value);
395
		} else {
396 2
			$value = array($value);
397
		}
398
399
		// saved entity. persist md to db.
400 2
		if ($this->guid) {
401
			// if overwriting, delete first.
402
			if (!$multiple) {
403
				$options = array(
404
					'guid' => $this->getGUID(),
405
					'metadata_name' => $name,
406
					'limit' => 0
407
				);
408
				// @todo in 1.9 make this return false if can't add metadata
409
				// https://github.com/elgg/elgg/issues/4520
410
				//
411
				// need to remove access restrictions right now to delete
412
				// because this is the expected behavior
413
				$ia = elgg_set_ignore_access(true);
414
				if (false === elgg_delete_metadata($options)) {
415
					return false;
416
				}
417
				elgg_set_ignore_access($ia);
418
			}
419
420
			$owner_guid = (int)$owner_guid;
421
			$access_id = ($access_id === null) ? $this->getAccessId() : (int)$access_id;
422
			$owner_guid = $owner_guid ? $owner_guid : $this->getOwnerGUID();
423
424
			// add new md
425
			$result = true;
426
			foreach ($value as $value_tmp) {
427
				// at this point $value is appended because it was cleared above if needed.
428
				$md_id = create_metadata($this->getGUID(), $name, $value_tmp, $value_type,
429
						$owner_guid, $access_id, true);
430
				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...
431
					return false;
432
				}
433
			}
434
435
			return $result;
436
		} else {
437
			// unsaved entity. store in temp array
438
439
			// returning single entries instead of an array of 1 element is decided in
440
			// getMetaData(), just like pulling from the db.
441
442 2
			if ($owner_guid != 0 || $access_id !== null) {
443
				$msg = "owner guid and access id cannot be used in \ElggEntity::setMetadata() until entity is saved.";
444
				throw new \InvalidArgumentException($msg);
445
			}
446
447
			// if overwrite, delete first
448 2
			if (!$multiple || !isset($this->temp_metadata[$name])) {
449 2
				$this->temp_metadata[$name] = array();
450 2
			}
451
452
			// add new md
453 2
			$this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
454 2
			return true;
455
		}
456
	}
457
458
	/**
459
	 * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
460
	 * If you pass a name, only metadata matching that name will be deleted.
461
	 *
462
	 * @warning Calling this with no $name will clear all metadata on the entity.
463
	 *
464
	 * @param null|string $name The name of the metadata to remove.
465
	 * @return bool
466
	 * @since 1.8
467
	 */
468
	public function deleteMetadata($name = null) {
469
470
		if (!$this->guid) {
471
			return false;
472
		}
473
474
		$options = array(
475
			'guid' => $this->guid,
476
			'limit' => 0
477
		);
478
		if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
479
			$options['metadata_name'] = $name;
480
		}
481
482
		return elgg_delete_metadata($options);
483
	}
484
485
	/**
486
	 * Deletes all metadata owned by this object (metadata.owner_guid = $this->guid).
487
	 * If you pass a name, only metadata matching that name will be deleted.
488
	 *
489
	 * @param null|string $name The name of metadata to delete.
490
	 * @return bool
491
	 * @since 1.8
492
	 */
493 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...
494
		// access is turned off for this because they might
495
		// no longer have access to an entity they created metadata on.
496
		$ia = elgg_set_ignore_access(true);
497
		$options = array(
498
			'metadata_owner_guid' => $this->guid,
499
			'limit' => 0
500
		);
501
		if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
502
			$options['metadata_name'] = $name;
503
		}
504
505
		$r = elgg_delete_metadata($options);
506
		elgg_set_ignore_access($ia);
507
		return $r;
508
	}
509
510
	/**
511
	 * Remove metadata
512
	 *
513
	 * @warning Calling this with no or empty arguments will clear all metadata on the entity.
514
	 *
515
	 * @param string $name The name of the metadata to clear
516
	 * @return mixed bool
517
	 * @deprecated 1.8 Use deleteMetadata()
518
	 */
519
	public function clearMetadata($name = '') {
520
		elgg_deprecated_notice('\ElggEntity->clearMetadata() is deprecated by ->deleteMetadata()', 1.8);
521
		return $this->deleteMetadata($name);
522
	}
523
524
	/**
525
	 * Disables metadata for this entity, optionally based on name.
526
	 *
527
	 * @param string $name An options name of metadata to disable.
528
	 * @return bool
529
	 * @since 1.8
530
	 */
531 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...
532
		$options = array(
533
			'guid' => $this->guid,
534
			'limit' => 0
535
		);
536
		if ($name) {
537
			$options['metadata_name'] = $name;
538
		}
539
540
		return elgg_disable_metadata($options);
541
	}
542
543
	/**
544
	 * Enables metadata for this entity, optionally based on name.
545
	 *
546
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
547
	 *
548
	 * @param string $name An options name of metadata to enable.
549
	 * @return bool
550
	 * @since 1.8
551
	 */
552 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...
553
		$options = array(
554
			'guid' => $this->guid,
555
			'limit' => 0
556
		);
557
		if ($name) {
558
			$options['metadata_name'] = $name;
559
		}
560
561
		return elgg_enable_metadata($options);
562
	}
563
564
	/**
565
	 * Get a piece of volatile (non-persisted) data on this entity.
566
	 *
567
	 * @param string $name The name of the volatile data
568
	 *
569
	 * @return mixed The value or null if not found.
570
	 */
571
	public function getVolatileData($name) {
572
		if (!is_array($this->volatile)) {
573
			$this->volatile = array();
574
		}
575
576
		if (array_key_exists($name, $this->volatile)) {
577
			return $this->volatile[$name];
578
		} else {
579
			return null;
580
		}
581
	}
582
583
	/**
584
	 * Set a piece of volatile (non-persisted) data on this entity
585
	 *
586
	 * @param string $name  Name
587
	 * @param mixed  $value Value
588
	 *
589
	 * @return void
590
	 */
591
	public function setVolatileData($name, $value) {
592
		if (!is_array($this->volatile)) {
593
			$this->volatile = array();
594
		}
595
596
		$this->volatile[$name] = $value;
597
	}
598
599
	/**
600
	 * Remove all relationships to and from this entity.
601
	 * If you pass a relationship name, only relationships matching that name
602
	 * will be deleted.
603
	 *
604
	 * @warning Calling this with no $relationship will clear all relationships
605
	 * for this entity.
606
	 *
607
	 * @param null|string $relationship The name of the relationship to remove.
608
	 * @return bool
609
	 * @see \ElggEntity::addRelationship()
610
	 * @see \ElggEntity::removeRelationship()
611
	 */
612
	public function deleteRelationships($relationship = null) {
613
		$relationship = (string)$relationship;
614
		$result = remove_entity_relationships($this->getGUID(), $relationship);
615
		return $result && remove_entity_relationships($this->getGUID(), $relationship, true);
616
	}
617
618
	/**
619
	 * Remove all relationships to and from this entity.
620
	 *
621
	 * @return bool
622
	 * @see \ElggEntity::addRelationship()
623
	 * @see \ElggEntity::removeRelationship()
624
	 * @deprecated 1.8 Use \ElggEntity::deleteRelationships()
625
	 */
626
	public function clearRelationships() {
627
		elgg_deprecated_notice('\ElggEntity->clearRelationships() is deprecated by ->deleteRelationships()', 1.8);
628
		return $this->deleteRelationships();
629
	}
630
631
	/**
632
	 * Add a relationship between this an another entity.
633
	 *
634
	 * @tip Read the relationship like "This entity is a $relationship of $guid_two."
635
	 *
636
	 * @param int    $guid_two     GUID of the target entity of the relationship.
637
	 * @param string $relationship The type of relationship.
638
	 *
639
	 * @return bool
640
	 * @see \ElggEntity::removeRelationship()
641
	 * @see \ElggEntity::deleteRelationships()
642
	 */
643
	public function addRelationship($guid_two, $relationship) {
644
		return add_entity_relationship($this->getGUID(), $relationship, $guid_two);
645
	}
646
647
	/**
648
	 * Remove a relationship
649
	 *
650
	 * @param int    $guid_two     GUID of the target entity of the relationship.
651
	 * @param string $relationship The type of relationship.
652
	 *
653
	 * @return bool
654
	 * @see \ElggEntity::addRelationship()
655
	 * @see \ElggEntity::deleteRelationships()
656
	 */
657
	public function removeRelationship($guid_two, $relationship) {
658
		return remove_entity_relationship($this->getGUID(), $relationship, $guid_two);
659
	}
660
661
	/**
662
	 * Adds a private setting to this entity.
663
	 *
664
	 * Private settings are similar to metadata but will not
665
	 * be searched and there are fewer helper functions for them.
666
	 *
667
	 * @param string $name  Name of private setting
668
	 * @param mixed  $value Value of private setting
669
	 *
670
	 * @return bool
671
	 */
672 9
	public function setPrivateSetting($name, $value) {
673 9
		if ((int) $this->guid > 0) {
674
			return set_private_setting($this->getGUID(), $name, $value);
675
		} else {
676 9
			$this->temp_private_settings[$name] = $value;
677 9
			return true;
678
		}
679
	}
680
681
	/**
682
	 * Returns a private setting value
683
	 *
684
	 * @param string $name Name of the private setting
685
	 *
686
	 * @return mixed Null if the setting does not exist
687
	 */
688 3
	public function getPrivateSetting($name) {
689 3
		if ((int) ($this->guid) > 0) {
690
			return get_private_setting($this->getGUID(), $name);
691
		} else {
692 3
			if (isset($this->temp_private_settings[$name])) {
693 2
				return $this->temp_private_settings[$name];
694
			}
695
		}
696 1
		return null;
697
	}
698
699
	/**
700
	 * Removes private setting
701
	 *
702
	 * @param string $name Name of the private setting
703
	 *
704
	 * @return bool
705
	 */
706
	public function removePrivateSetting($name) {
707
		return remove_private_setting($this->getGUID(), $name);
708
	}
709
710
	/**
711
	 * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
712
	 * If you pass a name, only annotations matching that name will be deleted.
713
	 *
714
	 * @warning Calling this with no or empty arguments will clear all annotations on the entity.
715
	 *
716
	 * @param null|string $name The annotations name to remove.
717
	 * @return bool
718
	 * @since 1.8
719
	 */
720 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...
721
		$options = array(
722
			'guid' => $this->guid,
723
			'limit' => 0
724
		);
725
		if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
726
			$options['annotation_name'] = $name;
727
		}
728
729
		return elgg_delete_annotations($options);
730
	}
731
732
	/**
733
	 * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
734
	 * If you pass a name, only annotations matching that name will be deleted.
735
	 *
736
	 * @param null|string $name The name of annotations to delete.
737
	 * @return bool
738
	 * @since 1.8
739
	 */
740 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...
741
		// access is turned off for this because they might
742
		// no longer have access to an entity they created annotations on.
743
		$ia = elgg_set_ignore_access(true);
744
		$options = array(
745
			'annotation_owner_guid' => $this->guid,
746
			'limit' => 0
747
		);
748
		if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
749
			$options['annotation_name'] = $name;
750
		}
751
752
		$r = elgg_delete_annotations($options);
753
		elgg_set_ignore_access($ia);
754
		return $r;
755
	}
756
757
	/**
758
	 * Disables annotations for this entity, optionally based on name.
759
	 *
760
	 * @param string $name An options name of annotations to disable.
761
	 * @return bool
762
	 * @since 1.8
763
	 */
764 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...
765
		$options = array(
766
			'guid' => $this->guid,
767
			'limit' => 0
768
		);
769
		if ($name) {
770
			$options['annotation_name'] = $name;
771
		}
772
773
		return elgg_disable_annotations($options);
774
	}
775
776
	/**
777
	 * Enables annotations for this entity, optionally based on name.
778
	 *
779
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
780
	 *
781
	 * @param string $name An options name of annotations to enable.
782
	 * @return bool
783
	 * @since 1.8
784
	 */
785 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...
786
		$options = array(
787
			'guid' => $this->guid,
788
			'limit' => 0
789
		);
790
		if ($name) {
791
			$options['annotation_name'] = $name;
792
		}
793
794
		return elgg_enable_annotations($options);
795
	}
796
797
	/**
798
	 * Helper function to return annotation calculation results
799
	 *
800
	 * @param string $name        The annotation name.
801
	 * @param string $calculation A valid MySQL function to run its values through
802
	 * @return mixed
803
	 */
804
	private function getAnnotationCalculation($name, $calculation) {
805
		$options = array(
806
			'guid' => $this->getGUID(),
807
			'distinct' => false,
808
			'annotation_name' => $name,
809
			'annotation_calculation' => $calculation
810
		);
811
812
		return elgg_get_annotations($options);
813
	}
814
815
	/**
816
	 * Adds an annotation to an entity.
817
	 *
818
	 * @warning By default, annotations are private.
819
	 *
820
	 * @warning Annotating an unsaved entity more than once with the same name
821
	 *          will only save the last annotation.
822
	 *
823
	 * @param string $name       Annotation name
824
	 * @param mixed  $value      Annotation value
825
	 * @param int    $access_id  Access ID
826
	 * @param int    $owner_guid GUID of the annotation owner
827
	 * @param string $vartype    The type of annotation value
828
	 *
829
	 * @return bool|int Returns int if an annotation is saved
830
	 */
831
	public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $vartype = "") {
832
		if ((int) $this->guid > 0) {
833
			return create_annotation($this->getGUID(), $name, $value, $vartype, $owner_guid, $access_id);
834
		} else {
835
			$this->temp_annotations[$name] = $value;
836
		}
837
		return true;
838
	}
839
840
	/**
841
	 * Gets an array of annotations.
842
	 *
843
	 * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
844
	 * as the options array.
845
	 *
846
	 * @param array  $options Array of options for elgg_get_annotations() except guid. This
847
	 *               may be passed a string annotation name, but this usage is deprecated.
848
	 * @param int    $limit   Limit (deprecated)
849
	 * @param int    $offset  Offset (deprecated)
850
	 * @param string $order   Order by time: asc or desc (deprecated)
851
	 *
852
	 * @return array
853
	 * @see elgg_get_annotations()
854
	 */
855
	public function getAnnotations($options = array(), $limit = 50, $offset = 0, $order = "asc") {
856
		if (!is_array($options)) {
857
			elgg_deprecated_notice("\ElggEntity::getAnnotations() takes an array of options.", 1.9);
858
		}
859
860
		if ((int) ($this->guid) > 0) {
861
			if (!is_array($options)) {
862
				$options = array(
863
					'guid' => $this->guid,
864
					'annotation_name' => $options,
865
					'limit' => $limit,
866
					'offset' => $offset,
867
				);
868
869
				if ($order != 'asc') {
870
					$options['reverse_order_by'] = true;
871
				}
872
			} else {
873
				$options['guid'] = $this->guid;
874
			}
875
876
			return elgg_get_annotations($options);
877
		} else {
878
			if (!is_array($options)) {
879
				$name = $options;
880
			} else {
881
				$name = elgg_extract('annotation_name', $options, '');
882
			}
883
884
			if (isset($this->temp_annotations[$name])) {
885
				return array($this->temp_annotations[$name]);
886
			}
887
		}
888
889
		return array();
890
	}
891
892
	/**
893
	 * Remove an annotation or all annotations for this entity.
894
	 *
895
	 * @warning Calling this method with no or an empty argument will remove
896
	 * all annotations on the entity.
897
	 *
898
	 * @param string $name Annotation name
899
	 * @return bool
900
	 * @deprecated 1.8 Use ->deleteAnnotations()
901
	 */
902
	public function clearAnnotations($name = "") {
903
		elgg_deprecated_notice('\ElggEntity->clearAnnotations() is deprecated by ->deleteAnnotations()', 1.8);
904
		return $this->deleteAnnotations($name);
905
	}
906
907
	/**
908
	 * Count annotations.
909
	 *
910
	 * @param string $name The type of annotation.
911
	 *
912
	 * @return int
913
	 */
914
	public function countAnnotations($name = "") {
915
		return $this->getAnnotationCalculation($name, 'count');
916
	}
917
918
	/**
919
	 * Get the average of an integer type annotation.
920
	 *
921
	 * @param string $name Annotation name
922
	 *
923
	 * @return int
924
	 */
925
	public function getAnnotationsAvg($name) {
926
		return $this->getAnnotationCalculation($name, 'avg');
927
	}
928
929
	/**
930
	 * Get the sum of integer type annotations of a given name.
931
	 *
932
	 * @param string $name Annotation name
933
	 *
934
	 * @return int
935
	 */
936
	public function getAnnotationsSum($name) {
937
		return $this->getAnnotationCalculation($name, 'sum');
938
	}
939
940
	/**
941
	 * Get the minimum of integer type annotations of given name.
942
	 *
943
	 * @param string $name Annotation name
944
	 *
945
	 * @return int
946
	 */
947
	public function getAnnotationsMin($name) {
948
		return $this->getAnnotationCalculation($name, 'min');
949
	}
950
951
	/**
952
	 * Get the maximum of integer type annotations of a given name.
953
	 *
954
	 * @param string $name Annotation name
955
	 *
956
	 * @return int
957
	 */
958
	public function getAnnotationsMax($name) {
959
		return $this->getAnnotationCalculation($name, 'max');
960
	}
961
962
	/**
963
	 * Count the number of comments attached to this entity.
964
	 *
965
	 * @return int Number of comments
966
	 * @since 1.8.0
967
	 */
968
	public function countComments() {
969
		$params = array('entity' => $this);
970
		$num = _elgg_services()->hooks->trigger('comments:count', $this->getType(), $params);
971
972
		if (is_int($num)) {
973
			return $num;
974
		} else {
975
			return elgg_get_entities(array(
976
				'type' => 'object',
977
				'subtype' => 'comment',
978
				'container_guid' => $this->getGUID(),
979
				'count' => true,
980
				'distinct' => false,
981
			));
982
		}
983
	}
984
985
	/**
986
	 * Gets an array of entities with a relationship to this entity.
987
	 *
988
	 * @param array $options Options array. See elgg_get_entities_from_relationship()
989
	 *                       for a list of options. 'relationship_guid' is set to
990
	 *                       this entity.
991
	 * @param bool  $inverse Is this an inverse relationship? (deprecated)
992
	 * @param int   $limit   Number of elements to return (deprecated)
993
	 * @param int   $offset  Indexing offset (deprecated)
994
	 *
995
	 * @return array|false An array of entities or false on failure
996
	 * @see elgg_get_entities_from_relationship()
997
	 */
998
	public function getEntitiesFromRelationship($options = array(), $inverse = false, $limit = 50, $offset = 0) {
999
		if (is_array($options)) {
1000
			$options['relationship_guid'] = $this->getGUID();
1001
			return elgg_get_entities_from_relationship($options);
1002
		} else {
1003
			elgg_deprecated_notice("\ElggEntity::getEntitiesFromRelationship takes an options array", 1.9);
1004
			return elgg_get_entities_from_relationship(array(
1005
				'relationship' => $options,
1006
				'relationship_guid' => $this->getGUID(),
1007
				'inverse_relationship' => $inverse,
1008
				'limit' => $limit,
1009
				'offset' => $offset
1010
			));
1011
		}
1012
	}
1013
1014
	/**
1015
	 * Gets the number of entities from a specific relationship type
1016
	 *
1017
	 * @param string $relationship         Relationship type (eg "friends")
1018
	 * @param bool   $inverse_relationship Invert relationship
1019
	 *
1020
	 * @return int|false The number of entities or false on failure
1021
	 */
1022
	public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
1023
		return elgg_get_entities_from_relationship(array(
1024
			'relationship' => $relationship,
1025
			'relationship_guid' => $this->getGUID(),
1026
			'inverse_relationship' => $inverse_relationship,
1027
			'count' => true
1028
		));
1029
	}
1030
1031
	/**
1032
	 * Can a user edit this entity?
1033
	 *
1034
	 * @tip Can be overridden by registering for the permissions_check plugin hook.
1035
	 *
1036
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
1037
	 *
1038
	 * @return bool Whether this entity is editable by the given user.
1039
	 * @see elgg_set_ignore_access()
1040
	 */
1041
	public function canEdit($user_guid = 0) {
1042
		$user_guid = (int)$user_guid;
1043
		$user = get_entity($user_guid);
1044
		if (!$user) {
1045
			$user = _elgg_services()->session->getLoggedInUser();
1046
		}
1047
1048
		$return = false;
1049
1050
		// Test user if possible - should default to false unless a plugin hook says otherwise
1051
		if ($user) {
1052
			if ($this->getOwnerGUID() == $user->getGUID()) {
1053
				$return = true;
1054
			}
1055
			
1056
			if ($this->getContainerGUID() == $user->getGUID()) {
1057
				$return = true;
1058
			}
1059
			
1060
			if ($this->getGUID() == $user->getGUID()) {
1061
				$return = true;
1062
			}
1063
1064
			$container = $this->getContainerEntity();
1065
			if ($container && $container->canEdit($user->getGUID())) {
1066
				$return = true;
1067
			}
1068
		}
1069
1070
		$params = array('entity' => $this, 'user' => $user);
1071
		return _elgg_services()->hooks->trigger('permissions_check', $this->type, $params, $return);
1072
	}
1073
1074
	/**
1075
	 * Can a user delete this entity?
1076
	 *
1077
	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
1078
	 *
1079
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
1080
	 *
1081
	 * @return bool Whether this entity is deletable by the given user.
1082
	 * @since 1.11
1083
	 * @see elgg_set_ignore_access()
1084
	 */
1085
	public function canDelete($user_guid = 0) {
1086
		$user_guid = (int) $user_guid;
1087
1088
		if (!$user_guid) {
1089
			$user_guid = _elgg_services()->session->getLoggedInUserGuid();
1090
		}
1091
1092
		// need to ignore access and show hidden entities for potential hidden/disabled users
1093
		$ia = elgg_set_ignore_access(true);
1094
		$show_hidden = access_show_hidden_entities(true);
1095
		
1096
		$user = _elgg_services()->entityTable->get($user_guid, 'user');
1097
		
1098
		elgg_set_ignore_access($ia);
1099
		access_show_hidden_entities($show_hidden);
1100
		
1101 View Code Duplication
		if ($user_guid & !$user) {
1102
			// requested to check access for a specific user_guid, but there is no user entity, so return false
1103
			$message = _elgg_services()->translator->translate('entity:can_delete:invaliduser', array($user_guid));
1104
			_elgg_services()->logger->warning($message);
0 ignored issues
show
Bug introduced by
The method warning() does not seem to exist on object<Elgg\Logger>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1105
			
1106
			return false;
1107
		}
1108
		
1109
		$return = $this->canEdit($user_guid);
1110
1111
		$params = array('entity' => $this, 'user' => $user);
1112
		return _elgg_services()->hooks->trigger('permissions_check:delete', $this->type, $params, $return);
1113
	}
1114
1115
	/**
1116
	 * Can a user edit metadata on this entity?
1117
	 *
1118
	 * If no specific metadata is passed, it returns whether the user can
1119
	 * edit any metadata on the entity.
1120
	 *
1121
	 * @tip Can be overridden by by registering for the permissions_check:metadata
1122
	 * plugin hook.
1123
	 *
1124
	 * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
1125
	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
1126
	 *
1127
	 * @return bool
1128
	 * @see elgg_set_ignore_access()
1129
	 */
1130
	public function canEditMetadata($metadata = null, $user_guid = 0) {
1131
		if (!$this->guid) {
1132
			// @todo cannot edit metadata on unsaved entity?
1133
			return false;
1134
		}
1135
1136 View Code Duplication
		if ($user_guid) {
1137
			$user = get_user($user_guid);
1138
			if (!$user) {
1139
				return false;
1140
			}
1141
		} else {
1142
			$user = _elgg_services()->session->getLoggedInUser();
1143
			$user_guid = $user->guid;
1144
		}
1145
1146
		$return = null;
1147
1148
		// if metadata is not owned or owned by the user, then can edit
1149
		if ($metadata && ($metadata->owner_guid == 0 || $metadata->owner_guid == $user_guid)) {
1150
			$return = true;
1151
		}
1152
1153
		if (is_null($return)) {
1154
			$return = $this->canEdit($user_guid);
1155
		}
1156
1157
		// metadata and user may be null
1158
		$params = array('entity' => $this, 'user' => $user, 'metadata' => $metadata);
1159
		return _elgg_services()->hooks->trigger('permissions_check:metadata', $this->type, $params, $return);
1160
	}
1161
1162
	/**
1163
	 * Can a user add an entity to this container
1164
	 *
1165
	 * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
1166
	 * @param string $type      The type of entity we're looking to write
1167
	 * @param string $subtype   The subtype of the entity we're looking to write
1168
	 *
1169
	 * @return bool
1170
	 * @see elgg_set_ignore_access()
1171
	 */
1172
	public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
1173
		return can_write_to_container($user_guid, $this->guid, $type, $subtype);
1174
	}
1175
1176
	/**
1177
	 * Can a user comment on an entity?
1178
	 *
1179
	 * @tip Can be overridden by registering for the permissions_check:comment,
1180
	 * <entity type> plugin hook.
1181
	 *
1182
	 * @param int $user_guid User guid (default is logged in user)
1183
	 *
1184
	 * @return bool
1185
	 */
1186
	public function canComment($user_guid = 0) {
1187
		if ($user_guid == 0) {
1188
			$user_guid = _elgg_services()->session->getLoggedInUserGuid();
1189
		}
1190
		$user = get_entity($user_guid);
1191
1192
		// By default, we don't take a position of whether commenting is allowed
1193
		// because it is handled by the subclasses of \ElggEntity
1194
		$params = array('entity' => $this, 'user' => $user);
1195
		return _elgg_services()->hooks->trigger('permissions_check:comment', $this->type, $params, null);
1196
	}
1197
1198
	/**
1199
	 * Can a user annotate an entity?
1200
	 *
1201
	 * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
1202
	 * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
1203
	 *
1204
	 * @tip If you want logged out users to annotate an object, do not call
1205
	 * canAnnotate(). It's easier than using the plugin hook.
1206
	 *
1207
	 * @param int    $user_guid       User guid (default is logged in user)
1208
	 * @param string $annotation_name The name of the annotation (default is unspecified)
1209
	 *
1210
	 * @return bool
1211
	 */
1212
	public function canAnnotate($user_guid = 0, $annotation_name = '') {
1213
		if ($user_guid == 0) {
1214
			$user_guid = _elgg_services()->session->getLoggedInUserGuid();
1215
		}
1216
		$user = get_entity($user_guid);
1217
1218
		$return = true;
1219
		if (!$user) {
1220
			$return = false;
1221
		}
1222
1223
		$hooks = _elgg_services()->hooks;
1224
1225
		$params = array(
1226
			'entity' => $this,
1227
			'user' => $user,
1228
			'annotation_name' => $annotation_name,
1229
		);
1230
		if ($annotation_name !== '') {
1231
			$return = $hooks->trigger("permissions_check:annotate:$annotation_name", $this->type, $params, $return);
1232
		}
1233
		$return = $hooks->trigger('permissions_check:annotate', $this->type, $params, $return);
1234
1235
		return $return;
1236
	}
1237
1238
	/**
1239
	 * Returns the access_id.
1240
	 *
1241
	 * @return int The access ID
1242
	 */
1243 1
	public function getAccessID() {
1244 1
		return $this->access_id;
1245
	}
1246
1247
	/**
1248
	 * Returns the guid.
1249
	 *
1250
	 * @return int|null GUID
1251
	 */
1252 8
	public function getGUID() {
1253 8
		return $this->guid;
1254
	}
1255
1256
	/**
1257
	 * Returns the entity type
1258
	 *
1259
	 * @return string The entity type
1260
	 */
1261 7
	public function getType() {
1262 7
		return $this->type;
1263
	}
1264
1265
	/**
1266
	 * Get the entity subtype
1267
	 *
1268
	 * @return string The entity subtype
1269
	 */
1270 7
	public function getSubtype() {
1271
		// If this object hasn't been saved, then return the subtype string.
1272 7
		if ($this->attributes['guid']) {
1273
			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 1273 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...
1274
		}
1275 7
		return $this->attributes['subtype'];
1276
	}
1277
1278
	/**
1279
	 * Get the guid of the entity's owner.
1280
	 *
1281
	 * @return int The owner GUID
1282
	 */
1283 2
	public function getOwnerGUID() {
1284 2
		return (int)$this->owner_guid;
1285
	}
1286
1287
	/**
1288
	 * Return the guid of the entity's owner.
1289
	 *
1290
	 * @return int The owner GUID
1291
	 * @deprecated 1.8 Use getOwnerGUID()
1292
	 */
1293
	public function getOwner() {
1294
		elgg_deprecated_notice("\ElggEntity::getOwner deprecated for \ElggEntity::getOwnerGUID", 1.8);
1295
		return $this->getOwnerGUID();
1296
	}
1297
1298
	/**
1299
	 * Gets the \ElggEntity that owns this entity.
1300
	 *
1301
	 * @return \ElggEntity The owning entity
1302
	 */
1303
	public function getOwnerEntity() {
1304
		return get_entity($this->owner_guid);
1305
	}
1306
1307
	/**
1308
	 * Set the container for this object.
1309
	 *
1310
	 * @param int $container_guid The ID of the container.
1311
	 *
1312
	 * @return bool
1313
	 */
1314
	public function setContainerGUID($container_guid) {
1315
		return $this->container_guid = (int)$container_guid;
1316
	}
1317
1318
	/**
1319
	 * Set the container for this object.
1320
	 *
1321
	 * @param int $container_guid The ID of the container.
1322
	 *
1323
	 * @return bool
1324
	 * @deprecated 1.8 use setContainerGUID()
1325
	 */
1326
	public function setContainer($container_guid) {
1327
		elgg_deprecated_notice("\ElggObject::setContainer deprecated for \ElggEntity::setContainerGUID", 1.8);
1328
		return $this->setContainerGUID('container_guid', $container_guid);
0 ignored issues
show
Unused Code introduced by
The call to ElggEntity::setContainerGUID() has too many arguments starting with $container_guid.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1329
	}
1330
1331
	/**
1332
	 * Gets the container GUID for this entity.
1333
	 *
1334
	 * @return int
1335
	 */
1336 1
	public function getContainerGUID() {
1337 1
		return (int)$this->container_guid;
1338
	}
1339
1340
	/**
1341
	 * Gets the container GUID for this entity.
1342
	 *
1343
	 * @return int
1344
	 * @deprecated 1.8 Use getContainerGUID()
1345
	 */
1346
	public function getContainer() {
1347
		elgg_deprecated_notice("\ElggObject::getContainer deprecated for \ElggEntity::getContainerGUID", 1.8);
1348
		return $this->getContainerGUID();
1349
	}
1350
1351
	/**
1352
	 * Get the container entity for this object.
1353
	 *
1354
	 * @return \ElggEntity
1355
	 * @since 1.8.0
1356
	 */
1357
	public function getContainerEntity() {
1358
		return get_entity($this->getContainerGUID());
1359
	}
1360
1361
	/**
1362
	 * Returns the UNIX epoch time that this entity was last updated
1363
	 *
1364
	 * @return int UNIX epoch time
1365
	 */
1366 2
	public function getTimeUpdated() {
1367 2
		return $this->time_updated;
1368
	}
1369
1370
	/**
1371
	 * Gets the URL for this entity.
1372
	 *
1373
	 * Plugins can register for the 'entity:url', <type> plugin hook to
1374
	 * customize the url for an entity.
1375
	 *
1376
	 * @return string The URL of the entity
1377
	 */
1378 1
	public function getURL() {
1379
		
1380 1
		$url = "";
1381
1382
		// @todo remove when elgg_register_entity_url_handler() has been removed
1383 1
		if ($this->guid) {
1384
			global $CONFIG;
1385
			if (isset($CONFIG->entity_url_handler[$this->getType()][$this->getSubtype()])) {
1386
				$function = $CONFIG->entity_url_handler[$this->getType()][$this->getSubtype()];
1387
				if (is_callable($function)) {
1388
					$url = call_user_func($function, $this);
1389
				}
1390
			} elseif (isset($CONFIG->entity_url_handler[$this->getType()]['all'])) {
1391
				$function = $CONFIG->entity_url_handler[$this->getType()]['all'];
1392
				if (is_callable($function)) {
1393
					$url = call_user_func($function, $this);
1394
				}
1395 View Code Duplication
			} elseif (isset($CONFIG->entity_url_handler['all']['all'])) {
1396
				$function = $CONFIG->entity_url_handler['all']['all'];
1397
				if (is_callable($function)) {
1398
					$url = call_user_func($function, $this);
1399
				}
1400
			}
1401
1402
			if ($url) {
1403
				$url = elgg_normalize_url($url);
1404
			}
1405
		}
1406
1407 1
		$type = $this->getType();
1408 1
		$params = array('entity' => $this);
1409 1
		$url = _elgg_services()->hooks->trigger('entity:url', $type, $params, $url);
1410
1411
		// @todo remove when \ElggEntity::setURL() has been removed
1412 1
		if (!empty($this->url_override)) {
1413
			$url = $this->url_override;
1414
		}
1415
1416 1
		return elgg_normalize_url($url);
1417
	}
1418
1419
	/**
1420
	 * Overrides the URL returned by getURL()
1421
	 *
1422
	 * @warning This override exists only for the life of the object.
1423
	 *
1424
	 * @param string $url The new item URL
1425
	 *
1426
	 * @return string The URL
1427
	 * @deprecated 1.9.0 See \ElggEntity::getURL() for details on the plugin hook
1428
	 */
1429
	public function setURL($url) {
1430
		elgg_deprecated_notice('\ElggEntity::setURL() has been replaced by the "entity:url" plugin hook', 1.9);
1431
		$this->url_override = $url;
1432
		return $url;
1433
	}
1434
1435
	/**
1436
	 * Get the URL for this entity's icon
1437
	 *
1438
	 * Plugins can register for the 'entity:icon:url', <type> plugin hook
1439
	 * to customize the icon for an entity.
1440
	 *
1441
	 * @param mixed $params A string defining the size of the icon (e.g. tiny, small, medium, large)
1442
	 *                      or an array of parameters including 'size'
1443
	 * @return string The URL
1444
	 * @since 1.8.0
1445
	 */
1446
	public function getIconURL($params = array()) {
1447
		if (is_array($params)) {
1448
			$size = elgg_extract('size', $params, 'medium');
1449
		} else {
1450
			$size = is_string($params) ? $params : 'medium';
1451
			$params = array();
1452
		}
1453
		$size = elgg_strtolower($size);
1454
1455
		if (isset($this->icon_override[$size])) {
1456
			elgg_deprecated_notice("icon_override on an individual entity is deprecated", 1.8);
1457
			return $this->icon_override[$size];
1458
		}
1459
1460
		$params['entity'] = $this;
1461
		$params['size'] = $size;
1462
1463
		$type = $this->getType();
1464
1465
		$url = _elgg_services()->hooks->trigger('entity:icon:url', $type, $params, null);
1466
		if ($url == null) {
1467
			$url = "_graphics/icons/default/$size.png";
1468
		}
1469
1470
		return elgg_normalize_url($url);
1471
	}
1472
1473
	/**
1474
	 * Returns a URL for the entity's icon.
1475
	 *
1476
	 * @param string $size Either 'large', 'medium', 'small' or 'tiny'
1477
	 *
1478
	 * @return string The url or false if no url could be worked out.
1479
	 * @deprecated 1.8 Use getIconURL()
1480
	 */
1481
	public function getIcon($size = 'medium') {
1482
		elgg_deprecated_notice("getIcon() deprecated by getIconURL()", 1.8);
1483
		return $this->getIconURL($size);
1484
	}
1485
1486
	/**
1487
	 * Set an icon override for an icon and size.
1488
	 *
1489
	 * @warning This override exists only for the life of the object.
1490
	 *
1491
	 * @param string $url  The url of the icon.
1492
	 * @param string $size The size its for.
1493
	 *
1494
	 * @return bool
1495
	 * @deprecated 1.8 See getIconURL() for the plugin hook to use
1496
	 */
1497
	public function setIcon($url, $size = 'medium') {
1498
		elgg_deprecated_notice("icon_override on an individual entity is deprecated", 1.8);
1499
1500
		$url = sanitise_string($url);
1501
		$size = sanitise_string($size);
1502
1503
		if (!$this->icon_override) {
1504
			$this->icon_override = array();
1505
		}
1506
		$this->icon_override[$size] = $url;
1507
1508
		return true;
1509
	}
1510
1511
	/**
1512
	 * Add this entity to a site
1513
	 *
1514
	 * This creates a 'member_of_site' relationship.
1515
	 *
1516
	 * @param \ElggSite $site The site to add this entity to
1517
	 *
1518
	 * @return bool
1519
	 * @todo add \ElggSite type hint once we have removed addToSite() from \ElggUser
1520
	 * and \ElggObject
1521
	 */
1522
	public function addToSite($site) {
1523
		if (!elgg_instanceof($site, 'site')) {
1524
			return false;
1525
		}
1526
1527
		return $site->addEntity($this);
1528
	}
1529
1530
	/**
1531
	 * Remove this entity from a site
1532
	 *
1533
	 * This deletes the 'member_of_site' relationship.
1534
	 *
1535
	 * @param \ElggSite $site The site to remove this entity from
1536
	 *
1537
	 * @return bool
1538
	 * @todo add \ElggSite type hint once we have removed addToSite() from \ElggUser
1539
	 */
1540
	public function removeFromSite($site) {
1541
		if (!elgg_instanceof($site, 'site')) {
1542
			return false;
1543
		}
1544
1545
		return $site->removeEntity($this);
1546
	}
1547
1548
	/**
1549
	 * Gets the sites this entity is a member of
1550
	 *
1551
	 * Site membership is determined by relationships and not site_guid.
1552
	 *
1553
	 * @param array $options Options array for elgg_get_entities_from_relationship()
1554
	 *                       Parameters set automatically by this method:
1555
	 *                       'relationship', 'relationship_guid', 'inverse_relationship'
1556
	 *
1557
	 * @return array
1558
	 * @todo add type hint when \ElggUser and \ElggObject have been updates
1559
	 */
1560 View Code Duplication
	public function getSites($options = array()) {
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...
1561
		$options['relationship'] = 'member_of_site';
1562
		$options['relationship_guid'] = $this->guid;
1563
		$options['inverse_relationship'] = false;
1564
		if (!isset($options['site_guid']) || !isset($options['site_guids'])) {
1565
			$options['site_guids'] = ELGG_ENTITIES_ANY_VALUE;
1566
		}
1567
1568
		return elgg_get_entities_from_relationship($options);
1569
	}
1570
1571
	/**
1572
	 * Tests to see whether the object has been fully loaded.
1573
	 *
1574
	 * @return bool
1575
	 */
1576
	public function isFullyLoaded() {
1577
		return ! ($this->tables_loaded < $this->tables_split);
1578
	}
1579
1580
	/**
1581
	 * Save an entity.
1582
	 *
1583
	 * @return bool|int
1584
	 * @throws InvalidParameterException
1585
	 * @throws IOException
1586
	 */
1587 1
	public function save() {
1588 1
		$guid = $this->getGUID();
1589 1
		if ($guid > 0) {
1590
			return $this->update();
1591
		} else {
1592 1
			$guid = $this->create();
1593
			if ($guid) {
1594
				if (_elgg_services()->events->trigger('create', $this->type, $this)) {
1595
					return $guid;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $guid; (integer) is incompatible with the return type declared by the abstract method ElggData::save of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1596
				} else {
1597
					// plugins that return false to event don't need to override the access system
1598
					$ia = elgg_set_ignore_access(true);
1599
					$this->delete();
1600
					elgg_set_ignore_access($ia);
1601
				}
1602
			}
1603
		}
1604
		
1605
		return false;
1606
	}
1607
	
1608
	/**
1609
	 * Create a new entry in the entities table.
1610
	 *
1611
	 * Saves the base information in the entities table for the entity.  Saving
1612
	 * the type-specific information is handled in the calling class method.
1613
	 *
1614
	 * @warning Entities must have an entry in both the entities table and their type table
1615
	 * or they will throw an exception when loaded.
1616
	 *
1617
	 * @return int The new entity's GUID
1618
	 * @throws InvalidParameterException If the entity's type has not been set.
1619
	 * @throws IOException If the new row fails to write to the DB.
1620
	 */
1621 1
	protected function create() {
1622 1
		global $CONFIG;
1623
1624
		// Using attribute array directly; get function does something special!
1625 1
		$type = $this->getDatabase()->sanitizeString($this->attributes['type']);
1626 1
		if ($type == "") {
1627 1
			throw new \InvalidParameterException("Entity type must be set.");
1628
		}
1629
		
1630
		$subtype = $this->attributes['subtype'];
1631
		$subtype_id = add_subtype($type, $subtype);
1632
		$owner_guid = (int)$this->attributes['owner_guid'];
1633
		$access_id = (int)$this->attributes['access_id'];
1634
		$now = (string)time();
1635
		$time_created = isset($this->attributes['time_created']) ? (int)$this->attributes['time_created'] : $now;
1636
1637
		$site_guid = $this->attributes['site_guid'];
1638
		if ($site_guid == 0) {
1639
			$site_guid = $CONFIG->site_guid;
1640
		}
1641
		$site_guid = (int)$site_guid;
1642
		
1643
		$container_guid = $this->attributes['container_guid'];
1644
		if ($container_guid == 0) {
1645
			$container_guid = $owner_guid;
1646
			$this->attributes['container_guid'] = $container_guid;
1647
		}
1648
		$container_guid = (int)$container_guid;
1649
1650
		if ($access_id == ACCESS_DEFAULT) {
1651
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h');
1652
		}
1653
1654
		$owner = $this->getOwnerEntity();
1655
		if ($owner && !$owner->canWriteToContainer(0, $type, $subtype)) {
1656
			return false;
1657
		}
1658
		
1659
		if ($owner_guid != $container_guid) {
1660
			$container = $this->getContainerEntity();
1661
			if ($container && !$container->canWriteToContainer(0, $type, $subtype)) {
1662
				return false;
1663
			}
1664
		}
1665
1666
		if ( $type == "object" ){
1667
			if ( $subtype == "mission-posted" || $subtype == "mission" || $subtype == "bookmarks" || $subtype == "groupforumtopic" || $subtype == "event_calendar" || $subtype == "thewire" || $subtype == "blog" || $subtype == "file" ){
1668
				$duplicate_check = md5( sanitize_string($this->title).sanitize_string($this->description) ) . $owner_guid . get_input('__elgg_ts');
1669
				try{
1670
					$result = $this->getDatabase()->insertData("INSERT into {$CONFIG->dbprefix}entities
1671
						(type, subtype, owner_guid, site_guid, container_guid,
1672
							access_id, time_created, time_updated, last_action, enabled, duplicate_check)
1673
						values
1674
						('$type', $subtype_id, $owner_guid, $site_guid, $container_guid,
1675
							$access_id, $time_created, $now, $now, 'no', '$duplicate_check')");
1676
				}
1677
				catch( DatabaseException $e ){
1678
					error_log("Duplication prevented: " . $e->getMessage());
1679
					return null;
1680
				}
1681
			}
1682 View Code Duplication
			else{
1683
				$result = $this->getDatabase()->insertData("INSERT into {$CONFIG->dbprefix}entities
1684
						(type, subtype, owner_guid, site_guid, container_guid,
1685
							access_id, time_created, time_updated, last_action, enabled)
1686
						values
1687
						('$type', $subtype_id, $owner_guid, $site_guid, $container_guid,
1688
							$access_id, $time_created, $now, $now, 'no')");
1689
			}
1690
		}
1691 View Code Duplication
		else {
1692
			$result = $this->getDatabase()->insertData("INSERT into {$CONFIG->dbprefix}entities
1693
				(type, subtype, owner_guid, site_guid, container_guid,
1694
					access_id, time_created, time_updated, last_action)
1695
				values
1696
				('$type', $subtype_id, $owner_guid, $site_guid, $container_guid,
1697
					$access_id, $time_created, $now, $now)");
1698
		}
1699
1700
		if (!$result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result 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...
1701
			throw new \IOException("Unable to save new object's base entity information!");
1702
		}
1703
	
1704
		// for BC with 1.8, ->subtype always returns ID, ->getSubtype() the string
1705
		$this->attributes['subtype'] = (int)$subtype_id;
1706
		$this->attributes['guid'] = (int)$result;
1707
		$this->attributes['time_created'] = (int)$time_created;
1708
		$this->attributes['time_updated'] = (int)$now;
1709
		$this->attributes['last_action'] = (int)$now;
1710
		$this->attributes['site_guid'] = (int)$site_guid;
1711
		$this->attributes['container_guid'] = (int)$container_guid;
1712
1713
		// Save any unsaved metadata
1714
		if (sizeof($this->temp_metadata) > 0) {
1715
			foreach ($this->temp_metadata as $name => $value) {
1716
				$this->$name = $value;
1717
			}
1718
			
1719
			$this->temp_metadata = array();
1720
		}
1721
1722
		// Save any unsaved annotations.
1723
		if (sizeof($this->temp_annotations) > 0) {
1724
			foreach ($this->temp_annotations as $name => $value) {
1725
				$this->annotate($name, $value);
1726
			}
1727
			
1728
			$this->temp_annotations = array();
1729
		}
1730
1731
		// Save any unsaved private settings.
1732
		if (sizeof($this->temp_private_settings) > 0) {
1733
			foreach ($this->temp_private_settings as $name => $value) {
1734
				$this->setPrivateSetting($name, $value);
1735
			}
1736
			
1737
			$this->temp_private_settings = array();
1738
		}
1739
1740
		_elgg_cache_entity($this);
1741
		
1742
		return $result;
1743
	}
1744
1745
	/**
1746
	 * Update the entity in the database.
1747
	 *
1748
	 * @return bool Whether the update was successful.
1749
	 *
1750
	 * @throws InvalidParameterException
1751
	 */
1752
	protected function update() {
1753
		global $CONFIG;
1754
1755
		// See #5600. This ensures canEdit() checks the BD persisted entity so it sees the
1756
		// persisted owner_guid, container_guid, etc.
1757
		_elgg_disable_caching_for_entity($this->guid);
1758
		$persisted_entity = get_entity($this->guid);
1759
		if (!$persisted_entity) {
1760
			// Why worry about this case? If access control was off when the user fetched this object but
1761
			// was turned back on again. Better to just bail than to turn access control off again.
1762
			return false;
1763
		}
1764
1765
		$allow_edit = $persisted_entity->canEdit();
1766
		unset($persisted_entity);
1767
1768
		if ($allow_edit) {
1769
			// give old update event a chance to stop the update
1770
			$allow_edit = _elgg_services()->events->trigger('update', $this->type, $this);
1771
		}
1772
1773
		_elgg_enable_caching_for_entity($this->guid);
1774
1775
		if (!$allow_edit) {
1776
			return false;
1777
		}
1778
1779
		// See #6225. We copy these after the update event in case a handler changed one of them.
1780
		$guid = (int)$this->guid;
1781
		$owner_guid = (int)$this->owner_guid;
1782
		$access_id = (int)$this->access_id;
1783
		$container_guid = (int)$this->container_guid;
1784
		$time_created = (int)$this->time_created;
1785
		$time = time();
1786
1787
		if ($access_id == ACCESS_DEFAULT) {
1788
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.php');
1789
		}
1790
		
1791
		$ret = $this->getDatabase()->updateData("UPDATE {$CONFIG->dbprefix}entities
1792
			set owner_guid='$owner_guid', access_id='$access_id',
1793
			container_guid='$container_guid', time_created='$time_created',
1794
			time_updated='$time' WHERE guid=$guid");
1795
		
1796
		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...
1797
1798
		// TODO(evan): Move this to \ElggObject?
1799
		if ($this instanceof \ElggObject) {
1800
			update_river_access_by_object($guid, $access_id);
1801
		}
1802
1803
		// If memcache is available then delete this entry from the cache
1804
		static $newentity_cache;
1805
		if ((!$newentity_cache) && (is_memcache_available())) {
1806
			$newentity_cache = new \ElggMemcache('new_entity_cache');
1807
		}
1808
		if ($newentity_cache) {
1809
			$newentity_cache->delete($guid);
1810
		}
1811
1812
		if ($ret !== false) {
1813
			$this->attributes['time_updated'] = $time;
1814
		}
1815
1816
		_elgg_cache_entity($this);
1817
1818
		// Handle cases where there was no error BUT no rows were updated!
1819
		return $ret !== false;
1820
	}
1821
1822
	/**
1823
	 * Loads attributes from the entities table into the object.
1824
	 *
1825
	 * @param mixed $guid GUID of entity or \stdClass object from entities table
1826
	 *
1827
	 * @return bool
1828
	 */
1829
	protected function load($guid) {
1830
		if ($guid instanceof \stdClass) {
1831
			$row = $guid;
1832
		} else {
1833
			$row = get_entity_as_row($guid);
1834
		}
1835
1836
		if ($row) {
1837
			// Create the array if necessary - all subclasses should test before creating
1838
			if (!is_array($this->attributes)) {
1839
				$this->attributes = array();
1840
			}
1841
1842
			// Now put these into the attributes array as core values
1843
			$objarray = (array) $row;
1844
			foreach ($objarray as $key => $value) {
1845
				$this->attributes[$key] = $value;
1846
			}
1847
1848
			// Increment the portion counter
1849
			if (!$this->isFullyLoaded()) {
1850
				$this->tables_loaded++;
1851
			}
1852
1853
			// guid needs to be an int  https://github.com/elgg/elgg/issues/4111
1854
			$this->attributes['guid'] = (int)$this->attributes['guid'];
1855
1856
			// for BC with 1.8, ->subtype always returns ID, ->getSubtype() the string
1857
			$this->attributes['subtype'] = (int)$this->attributes['subtype'];
1858
1859
			// Cache object handle
1860
			if ($this->attributes['guid']) {
1861
				_elgg_cache_entity($this);
1862
			}
1863
1864
			return true;
1865
		}
1866
1867
		return false;
1868
	}
1869
1870
	/**
1871
	 * Stores non-attributes from the loading of the entity as volatile data
1872
	 *
1873
	 * @param array $data Key value array
1874
	 * @return void
1875
	 */
1876
	protected function loadAdditionalSelectValues(array $data) {
1877
		foreach ($data as $name => $value) {
1878
			$this->setVolatileData("select:$name", $value);
1879
		}
1880
	}
1881
	
1882
	/**
1883
	 * Load new data from database into existing entity. Overwrites data but
1884
	 * does not change values not included in the latest data.
1885
	 *
1886
	 * @internal This is used when the same entity is selected twice during a
1887
	 * request in case different select clauses were used to load different data
1888
	 * into volatile data.
1889
	 *
1890
	 * @param \stdClass $row DB row with new entity data
1891
	 * @return bool
1892
	 * @access private
1893
	 */
1894
	public function refresh(\stdClass $row) {
1895
		if ($row instanceof \stdClass) {
1896
			return $this->load($row);
1897
		}
1898
		return false;
1899
	}
1900
1901
	/**
1902
	 * Disable this entity.
1903
	 *
1904
	 * Disabled entities are not returned by getter functions.
1905
	 * To enable an entity, use {@link \ElggEntity::enable()}.
1906
	 *
1907
	 * Recursively disabling an entity will disable all entities
1908
	 * owned or contained by the parent entity.
1909
	 *
1910
	 * You can ignore the disabled field by using {@link access_show_hidden_entities()}.
1911
	 *
1912
	 * @note Internal: Disabling an entity sets the 'enabled' column to 'no'.
1913
	 *
1914
	 * @param string $reason    Optional reason
1915
	 * @param bool   $recursive Recursively disable all contained entities?
1916
	 *
1917
	 * @return bool
1918
	 * @see \ElggEntity::enable()
1919
	 */
1920 1
	public function disable($reason = "", $recursive = true) {
1921 1
		if (!$this->guid) {
1922 1
			return false;
1923
		}
1924
		
1925
		if (!_elgg_services()->events->trigger('disable', $this->type, $this)) {
1926
			return false;
1927
		}
1928
		
1929
		if (!$this->canEdit()) {
1930
			return false;
1931
		}
1932
1933
		if ($this instanceof ElggUser && $this->banned === 'no') {
1934
			// temporarily ban to prevent using the site during disable
1935
			_elgg_services()->usersTable->markBanned($this->guid, true);
1936
			$unban_after = true;
1937
		} else {
1938
			$unban_after = false;
1939
		}
1940
1941
		_elgg_invalidate_cache_for_entity($this->guid);
1942
		
1943
		if ($reason) {
1944
			$this->disable_reason = $reason;
1945
		}
1946
1947
		global $CONFIG;
1948
		$guid = (int)$this->guid;
1949
		
1950
		if ($recursive) {
1951
			$hidden = access_get_show_hidden_status();
1952
			access_show_hidden_entities(true);
1953
			$ia = elgg_set_ignore_access(true);
1954
1955
			$query = "
1956
				SELECT *
1957
				FROM {$CONFIG->dbprefix}entities
1958
				WHERE (
1959
					container_guid = $guid
1960
					OR owner_guid = $guid
1961
					OR site_guid = $guid
1962
				) 
1963
				AND enabled = 'yes'
1964
			";
1965
			$sub_entities = $this->getDatabase()->getData($query, 'entity_row_to_elggstar');
1966
1967
			if ($sub_entities) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sub_entities 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...
1968
				/* @var ElggEntity[] $sub_entities */
1969
				foreach ($sub_entities as $e) {
1970
					add_entity_relationship($e->guid, 'disabled_with', $this->guid);
1971
					$e->disable($reason);
1972
				}
1973
			}
1974
			
1975
			access_show_hidden_entities($hidden);
1976
			elgg_set_ignore_access($ia);
1977
		}
1978
1979
		$this->disableMetadata();
1980
		$this->disableAnnotations();
1981
1982
		$res = $this->getDatabase()->updateData("
1983
			UPDATE {$CONFIG->dbprefix}entities
1984
			SET enabled = 'no'
1985
			WHERE guid = $guid
1986
		");
1987
1988
		if ($unban_after) {
1989
			_elgg_services()->usersTable->markBanned($this->guid, false);
1990
		}
1991
1992
		if ($res) {
1993
			$this->attributes['enabled'] = 'no';
1994
			_elgg_services()->events->trigger('disable:after', $this->type, $this);
1995
		}
1996
1997
		return $res;
1998
	}
1999
2000
	/**
2001
	 * Enable the entity
2002
	 *
2003
	 * @warning Disabled entities can't be loaded unless
2004
	 * {@link access_show_hidden_entities(true)} has been called.
2005
	 *
2006
	 * @param bool $recursive Recursively enable all entities disabled with the entity?
2007
	 * @see access_show_hiden_entities()
2008
	 * @return bool
2009
	 */
2010
	public function enable($recursive = true) {
2011
		$guid = (int)$this->guid;
2012
		if (!$guid) {
2013
			return false;
2014
		}
2015
		
2016
		if (!_elgg_services()->events->trigger('enable', $this->type, $this)) {
2017
			return false;
2018
		}
2019
		
2020
		if (!$this->canEdit()) {
2021
			return false;
2022
		}
2023
		
2024
		global $CONFIG;
2025
	
2026
		// Override access only visible entities
2027
		$old_access_status = access_get_show_hidden_status();
2028
		access_show_hidden_entities(true);
2029
	
2030
		$result = $this->getDatabase()->updateData("UPDATE {$CONFIG->dbprefix}entities
2031
			SET enabled = 'yes'
2032
			WHERE guid = $guid");
2033
2034
		$this->deleteMetadata('disable_reason');
2035
		$this->enableMetadata();
2036
		$this->enableAnnotations();
2037
2038 View Code Duplication
		if ($recursive) {
2039
			$disabled_with_it = elgg_get_entities_from_relationship(array(
2040
				'relationship' => 'disabled_with',
2041
				'relationship_guid' => $guid,
2042
				'inverse_relationship' => true,
2043
				'limit' => 0,
2044
			));
2045
2046
			foreach ($disabled_with_it as $e) {
2047
				$e->enable();
2048
				remove_entity_relationship($e->guid, 'disabled_with', $guid);
2049
			}
2050
		}
2051
	
2052
		access_show_hidden_entities($old_access_status);
2053
	
2054
		if ($result) {
2055
			$this->attributes['enabled'] = 'yes';
2056
			_elgg_services()->events->trigger('enable:after', $this->type, $this);
2057
		}
2058
2059
		return $result;
2060
	}
2061
2062
	/**
2063
	 * Is this entity enabled?
2064
	 *
2065
	 * @return boolean Whether this entity is enabled.
2066
	 */
2067 1
	public function isEnabled() {
2068 1
		return $this->enabled == 'yes';
2069
	}
2070
2071
	/**
2072
	 * Deletes the entity.
2073
	 *
2074
	 * Removes the entity and its metadata, annotations, relationships,
2075
	 * river entries, and private data.
2076
	 *
2077
	 * Optionally can remove entities contained and owned by this entity.
2078
	 *
2079
	 * @warning If deleting recursively, this bypasses ownership of items contained by
2080
	 * the entity.  That means that if the container_guid = $this->guid, the item will
2081
	 * be deleted regardless of who owns it.
2082
	 *
2083
	 * @param bool $recursive If true (default) then all entities which are
2084
	 *                        owned or contained by $this will also be deleted.
2085
	 *
2086
	 * @return bool
2087
	 */
2088
	public function delete($recursive = true) {
2089
		global $CONFIG;
2090
2091
		$guid = $this->guid;
2092
		if (!$guid) {
2093
			return false;
2094
		}
2095
		
2096
		// first check if we can delete this entity
2097
		// NOTE: in Elgg <= 1.10.3 this was after the delete event,
2098
		// which could potentially remove some content if the user didn't have access
2099
		if (!$this->canDelete()) {
2100
			return false;
2101
		}
2102
2103
		// now trigger an event to let others know this entity is about to be deleted
2104
		// so they can prevent it or take their own actions
2105
		if (!_elgg_services()->events->trigger('delete', $this->type, $this)) {
2106
			return false;
2107
		}
2108
2109
		if ($this instanceof ElggUser) {
2110
			// ban to prevent using the site during delete
2111
			_elgg_services()->usersTable->markBanned($this->guid, true);
2112
		}
2113
2114
		_elgg_invalidate_cache_for_entity($guid);
2115
		
2116
		// If memcache is available then delete this entry from the cache
2117
		static $newentity_cache;
2118
		if ((!$newentity_cache) && (is_memcache_available())) {
2119
			$newentity_cache = new \ElggMemcache('new_entity_cache');
2120
		}
2121
		if ($newentity_cache) {
2122
			$newentity_cache->delete($guid);
2123
		}
2124
2125
		// Delete contained owned and otherwise releated objects (depth first)
2126
		if ($recursive) {
2127
			// Temporarily overriding access controls
2128
			$entity_disable_override = access_get_show_hidden_status();
2129
			access_show_hidden_entities(true);
2130
			$ia = elgg_set_ignore_access(true);
2131
2132
			// @todo there was logic in the original code that ignored
2133
			// entities with owner or container guids of themselves.
2134
			// this should probably be prevented in \ElggEntity instead of checked for here
2135
			$options = array(
2136
				'wheres' => array(
2137
					"((container_guid = $guid OR owner_guid = $guid OR site_guid = $guid)"
2138
					. " AND guid != $guid)"
2139
					),
2140
				'limit' => 0
2141
			);
2142
2143
			$batch = new \ElggBatch('elgg_get_entities', $options);
2144
			$batch->setIncrementOffset(false);
2145
2146
			foreach ($batch as $e) {
2147
				$e->delete(true);
2148
			}
2149
2150
			access_show_hidden_entities($entity_disable_override);
2151
			elgg_set_ignore_access($ia);
2152
		}
2153
2154
		$entity_disable_override = access_get_show_hidden_status();
2155
		access_show_hidden_entities(true);
2156
		$ia = elgg_set_ignore_access(true);
2157
		
2158
		// Now delete the entity itself
2159
		$this->deleteMetadata();
2160
		$this->deleteOwnedMetadata();
2161
		$this->deleteAnnotations();
2162
		$this->deleteOwnedAnnotations();
2163
		$this->deleteRelationships();
2164
		$this->deleteAccessCollectionMemberships();
2165
		$this->deleteOwnedAccessCollections();
2166
2167
		access_show_hidden_entities($entity_disable_override);
2168
		elgg_set_ignore_access($ia);
2169
2170
		elgg_delete_river(array('subject_guid' => $guid));
2171
		elgg_delete_river(array('object_guid' => $guid));
2172
		elgg_delete_river(array('target_guid' => $guid));
2173
		remove_all_private_settings($guid);
2174
2175
		$res = $this->getDatabase()->deleteData("
2176
			DELETE FROM {$CONFIG->dbprefix}entities
2177
			WHERE guid = $guid
2178
		");
2179
2180
		if ($res && in_array($this->type, ['object', 'user', 'group', 'site'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $res of type integer|false 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...
2181
			// delete from secondary table
2182
			$sub_table = "{$CONFIG->dbprefix}{$this->type}s_entity";
2183
2184
			$this->getDatabase()->deleteData("
2185
				DELETE FROM $sub_table
2186
				WHERE guid = $guid
2187
			");
2188
		}
2189
		
2190
		_elgg_clear_entity_files($this);
2191
2192
		return (bool)$res;
2193
	}
2194
2195
	/**
2196
	 * {@inheritdoc}
2197
	 */
2198 1
	public function toObject() {
2199 1
		$object = $this->prepareObject(new \stdClass());
2200 1
		$params = array('entity' => $this);
2201 1
		$object = _elgg_services()->hooks->trigger('to:object', 'entity', $params, $object);
2202 1
		return $object;
2203
	}
2204
2205
	/**
2206
	 * Prepare an object copy for toObject()
2207
	 *
2208
	 * @param \stdClass $object Object representation of the entity
2209
	 * @return \stdClass
2210
	 */
2211 1
	protected function prepareObject($object) {
2212 1
		$object->guid = $this->guid;
2213 1
		$object->type = $this->getType();
2214 1
		$object->subtype = $this->getSubtype();
2215 1
		$object->owner_guid = $this->getOwnerGUID();
2216 1
		$object->container_guid = $this->getContainerGUID();
2217 1
		$object->site_guid = (int)$this->site_guid;
2218 1
		$object->time_created = date('c', $this->getTimeCreated());
2219 1
		$object->time_updated = date('c', $this->getTimeUpdated());
2220 1
		$object->url = $this->getURL();
2221 1
		$object->read_access = (int)$this->access_id;
2222 1
		return $object;
2223
	}
2224
2225
	/*
2226
	 * LOCATABLE INTERFACE
2227
	 */
2228
2229
	/**
2230
	 * Gets the 'location' metadata for the entity
2231
	 *
2232
	 * @return string The location
2233
	 */
2234
	public function getLocation() {
2235
		return $this->location;
2236
	}
2237
2238
	/**
2239
	 * Sets the 'location' metadata for the entity
2240
	 *
2241
	 * @param string $location String representation of the location
2242
	 *
2243
	 * @return void
2244
	 */
2245
	public function setLocation($location) {
2246
		$this->location = $location;
2247
	}
2248
2249
	/**
2250
	 * Set latitude and longitude metadata tags for a given entity.
2251
	 *
2252
	 * @param float $lat  Latitude
2253
	 * @param float $long Longitude
2254
	 *
2255
	 * @return void
2256
	 * @todo Unimplemented
2257
	 */
2258 1
	public function setLatLong($lat, $long) {
2259 1
		$this->{"geo:lat"} = $lat;
2260 1
		$this->{"geo:long"} = $long;
2261 1
	}
2262
2263
	/**
2264
	 * Return the entity's latitude.
2265
	 *
2266
	 * @return float
2267
	 * @todo Unimplemented
2268
	 */
2269 1
	public function getLatitude() {
2270 1
		return (float)$this->{"geo:lat"};
2271
	}
2272
2273
	/**
2274
	 * Return the entity's longitude
2275
	 *
2276
	 * @return float
2277
	 * @todo Unimplemented
2278
	 */
2279 1
	public function getLongitude() {
2280 1
		return (float)$this->{"geo:long"};
2281
	}
2282
2283
	/*
2284
	 * NOTABLE INTERFACE
2285
	 */
2286
2287
	/**
2288
	 * Set the time and duration of an object
2289
	 *
2290
	 * @param int $hour     If ommitted, now is assumed.
2291
	 * @param int $minute   If ommitted, now is assumed.
2292
	 * @param int $second   If ommitted, now is assumed.
2293
	 * @param int $day      If ommitted, now is assumed.
2294
	 * @param int $month    If ommitted, now is assumed.
2295
	 * @param int $year     If ommitted, now is assumed.
2296
	 * @param int $duration Duration of event, remainder of the day is assumed.
2297
	 *
2298
	 * @return true
2299
	 * @deprecated 1.9
2300
	 */
2301
	public function setCalendarTimeAndDuration($hour = null, $minute = null, $second = null,
2302
	$day = null, $month = null, $year = null, $duration = null) {
2303
		elgg_deprecated_notice(__METHOD__ . ' has been deprecated', 1.9);
2304
2305
		$start = mktime($hour, $minute, $second, $month, $day, $year);
2306
		$end = $start + abs($duration);
2307
		if (!$duration) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $duration of type integer|null 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...
2308
			$end = get_day_end($day, $month, $year);
2309
		}
2310
2311
		$this->calendar_start = $start;
2312
		$this->calendar_end = $end;
2313
2314
		return true;
2315
	}
2316
2317
	/**
2318
	 * Returns the start timestamp.
2319
	 *
2320
	 * @return int
2321
	 * @deprecated 1.9
2322
	 */
2323
	public function getCalendarStartTime() {
2324
		elgg_deprecated_notice(__METHOD__ . ' has been deprecated', 1.9);
2325
		return (int)$this->calendar_start;
2326
	}
2327
2328
	/**
2329
	 * Returns the end timestamp.
2330
	 *
2331
	 * @return int
2332
	 * @deprecated 1.9
2333
	 */
2334
	public function getCalendarEndTime() {
2335
		elgg_deprecated_notice(__METHOD__ . ' has been deprecated', 1.9);
2336
		return (int)$this->calendar_end;
2337
	}
2338
2339
	/*
2340
	 * EXPORTABLE INTERFACE
2341
	 */
2342
2343
	/**
2344
	 * Returns an array of fields which can be exported.
2345
	 *
2346
	 * @return array
2347
	 * @deprecated 1.9 Use toObject()
2348
	 */
2349
	public function getExportableValues() {
2350
		elgg_deprecated_notice(__METHOD__ . ' has been deprecated by toObject()', 1.9);
2351
		return array(
2352
			'guid',
2353
			'type',
2354
			'subtype',
2355
			'time_created',
2356
			'time_updated',
2357
			'container_guid',
2358
			'owner_guid',
2359
			'site_guid'
2360
		);
2361
	}
2362
2363
	/**
2364
	 * Export this class into an array of ODD Elements containing all necessary fields.
2365
	 * Override if you wish to return more information than can be found in
2366
	 * $this->attributes (shouldn't happen)
2367
	 *
2368
	 * @return array
2369
	 * @deprecated 1.9
2370
	 */
2371
	public function export() {
2372
		elgg_deprecated_notice(__METHOD__ . ' has been deprecated', 1.9);
2373
		$tmp = array();
2374
2375
		// Generate uuid
2376
		$uuid = guid_to_uuid($this->getGUID());
2377
2378
		// Create entity
2379
		$odd = new ODDEntity(
0 ignored issues
show
Deprecated Code introduced by
The class ODDEntity has been deprecated with message: 1.9

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
2380
			$uuid,
2381
			$this->attributes['type'],
2382
			get_subtype_from_id($this->attributes['subtype'])
0 ignored issues
show
Security Bug introduced by
It seems like get_subtype_from_id($this->attributes['subtype']) targeting get_subtype_from_id() can also be of type false; however, ODDEntity::__construct() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
2383
		);
2384
2385
		$tmp[] = $odd;
2386
2387
		$exportable_values = $this->getExportableValues();
0 ignored issues
show
Deprecated Code introduced by
The method ElggEntity::getExportableValues() has been deprecated with message: 1.9 Use toObject()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2388
2389
		// Now add its attributes
2390
		foreach ($this->attributes as $k => $v) {
2391
			$meta = null;
2392
2393
			if (in_array($k, $exportable_values)) {
2394
				switch ($k) {
2395
					case 'guid':			// Dont use guid in OpenDD
2396
					case 'type':			// Type and subtype already taken care of
2397
					case 'subtype':
2398
						break;
2399
2400
					case 'time_created':	// Created = published
2401
						$odd->setAttribute('published', date("r", $v));
2402
						break;
2403
2404 View Code Duplication
					case 'site_guid':	// Container
2405
						$k = 'site_uuid';
2406
						$v = guid_to_uuid($v);
2407
						$meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
0 ignored issues
show
Deprecated Code introduced by
The class ODDMetaData has been deprecated with message: 1.9

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
2408
						break;
2409
2410 View Code Duplication
					case 'container_guid':	// Container
2411
						$k = 'container_uuid';
2412
						$v = guid_to_uuid($v);
2413
						$meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
0 ignored issues
show
Deprecated Code introduced by
The class ODDMetaData has been deprecated with message: 1.9

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
2414
						break;
2415
2416 View Code Duplication
					case 'owner_guid':			// Convert owner guid to uuid, this will be stored in metadata
2417
						$k = 'owner_uuid';
2418
						$v = guid_to_uuid($v);
2419
						$meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
0 ignored issues
show
Deprecated Code introduced by
The class ODDMetaData has been deprecated with message: 1.9

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
2420
						break;
2421
2422
					default:
2423
						$meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
0 ignored issues
show
Deprecated Code introduced by
The class ODDMetaData has been deprecated with message: 1.9

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
2424
				}
2425
2426
				// set the time of any metadata created
2427
				if ($meta) {
2428
					$meta->setAttribute('published', date("r", $this->time_created));
2429
					$tmp[] = $meta;
2430
				}
2431
			}
2432
		}
2433
2434
		// Now we do something a bit special.
2435
		/*
2436
		 * This provides a rendered view of the entity to foreign sites.
2437
		 */
2438
2439
		elgg_set_viewtype('default');
2440
		$view = elgg_view_entity($this, array('full_view' => true));
2441
		elgg_set_viewtype();
2442
2443
		$tmp[] = new ODDMetaData($uuid . "volatile/renderedentity/", $uuid,
0 ignored issues
show
Deprecated Code introduced by
The class ODDMetaData has been deprecated with message: 1.9

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
2444
			'renderedentity', $view, 'volatile');
0 ignored issues
show
Security Bug introduced by
It seems like $view defined by elgg_view_entity($this, ...y('full_view' => true)) on line 2440 can also be of type false; however, ODDMetaData::__construct() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2445
2446
		return $tmp;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $tmp; (array<ODDEntity|ODDMetaData>) is incompatible with the return type declared by the interface Exportable::export of type object.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2447
	}
2448
2449
	/*
2450
	 * IMPORTABLE INTERFACE
2451
	 */
2452
2453
	/**
2454
	 * Import data from an parsed ODD xml data array.
2455
	 *
2456
	 * @param ODD $data XML data
2457
	 *
2458
	 * @return true
2459
	 *
2460
	 * @throws InvalidParameterException
2461
	 * @deprecated 1.9 Use toObject()
2462
	 */
2463
	public function import(ODD $data) {
2464
		elgg_deprecated_notice(__METHOD__ . ' has been deprecated', 1.9);
2465
		if (!($data instanceof ODDEntity)) {
2466
			throw new \InvalidParameterException("import() passed an unexpected ODD class");
2467
		}
2468
2469
		// Set type and subtype
2470
		$this->attributes['type'] = $data->getAttribute('class');
2471
		$this->attributes['subtype'] = $data->getAttribute('subclass');
2472
2473
		// Set owner
2474
		$this->attributes['owner_guid'] = _elgg_services()->session->getLoggedInUserGuid(); // Import as belonging to importer.
2475
2476
		// Set time
2477
		$this->attributes['time_created'] = strtotime($data->getAttribute('published'));
2478
		$this->attributes['time_updated'] = time();
2479
2480
		return true;
2481
	}
2482
2483
	/*
2484
	 * SYSTEM LOG INTERFACE
2485
	 */
2486
2487
	/**
2488
	 * Return an identification for the object for storage in the system log.
2489
	 * This id must be an integer.
2490
	 *
2491
	 * @return int
2492
	 */
2493
	public function getSystemLogID() {
2494
		return $this->getGUID();
2495
	}
2496
2497
	/**
2498
	 * For a given ID, return the object associated with it.
2499
	 * This is used by the system log. It can be called on any Loggable object.
2500
	 *
2501
	 * @param int $id GUID.
2502
	 * @return int GUID
2503
	 */
2504
	public function getObjectFromID($id) {
2505
		return get_entity($id);
2506
	}
2507
2508
	/**
2509
	 * Returns tags for this entity.
2510
	 *
2511
	 * @warning Tags must be registered by {@link elgg_register_tag_metadata_name()}.
2512
	 *
2513
	 * @param array $tag_names Optionally restrict by tag metadata names.
2514
	 *
2515
	 * @return array
2516
	 */
2517
	public function getTags($tag_names = null) {
2518
		if ($tag_names && !is_array($tag_names)) {
2519
			$tag_names = array($tag_names);
2520
		}
2521
2522
		$valid_tags = elgg_get_registered_tag_metadata_names();
2523
		$entity_tags = array();
2524
2525
		foreach ($valid_tags as $tag_name) {
2526
			if (is_array($tag_names) && !in_array($tag_name, $tag_names)) {
2527
				continue;
2528
			}
2529
2530
			if ($tags = $this->$tag_name) {
2531
				// if a single tag, metadata returns a string.
2532
				// if multiple tags, metadata returns an array.
2533
				if (is_array($tags)) {
2534
					$entity_tags = array_merge($entity_tags, $tags);
2535
				} else {
2536
					$entity_tags[] = $tags;
2537
				}
2538
			}
2539
		}
2540
2541
		return $entity_tags;
2542
	}
2543
	
2544
	/**
2545
	 * Remove the membership of all access collections for this entity (if the entity is a user)
2546
	 *
2547
	 * @return bool
2548
	 * @since 1.11
2549
	 */
2550
	public function deleteAccessCollectionMemberships() {
2551
	
2552
		if (!$this->guid) {
2553
			return false;
2554
		}
2555
		
2556
		if ($this->type !== 'user') {
2557
			return true;
2558
		}
2559
		
2560
		$ac = _elgg_services()->accessCollections;
2561
		
2562
		$collections = $ac->getCollectionsByMember($this->guid);
2563
		if (empty($collections)) {
2564
			return true;
2565
		}
2566
		
2567
		$result = true;
2568
		foreach ($collections as $collection) {
2569
			$result = $result & $ac->removeUser($this->guid, $collection->id);
2570
		}
2571
		
2572
		return $result;
2573
	}
2574
	
2575
	/**
2576
	 * Remove all access collections owned by this entity
2577
	 *
2578
	 * @return bool
2579
	 * @since 1.11
2580
	 */
2581
	public function deleteOwnedAccessCollections() {
2582
		
2583
		if (!$this->guid) {
2584
			return false;
2585
		}
2586
		
2587
		$ac = _elgg_services()->accessCollections;
2588
		
2589
		$collections = $ac->getEntityCollections($this->guid);
2590
		if (empty($collections)) {
2591
			return true;
2592
		}
2593
		
2594
		$result = true;
2595
		foreach ($collections as $collection) {
2596
			$result = $result & $ac->delete($collection->id);
2597
		}
2598
		
2599
		return $result;
2600
	}
2601
}
2602