engine/classes/ElggEntity.php
Rama 7.x · réplica de lectura del repositorio original.
<?php
use Elgg\Database\QueryBuilder;
use Elgg\Exceptions\DatabaseException;
use Elgg\Exceptions\Filesystem\IOException;
use Elgg\Exceptions\DomainException as ElggDomainException;
use Elgg\Exceptions\InvalidArgumentException as ElggInvalidArgumentException;
use Elgg\Traits\Entity\AccessCollections;
use Elgg\Traits\Entity\Annotations;
use Elgg\Traits\Entity\Icons;
use Elgg\Traits\Entity\Metadata;
use Elgg\Traits\Entity\Relationships;
use Elgg\Traits\Entity\Subscriptions;
/**
* The parent class for all Elgg Entities.
*
* An \ElggEntity is one of the basic data models in Elgg.
* It is the primary means of storing and retrieving data from the database.
* An \ElggEntity represents one row of the entities table.
*
* The \ElggEntity class handles CRUD operations for the entities table.
* \ElggEntity should always be extended by another class to handle CRUD
* operations on the type-specific table.
*
* \ElggEntity uses magic methods for get and set, so any property that isn't
* declared will be assumed to be metadata and written to the database
* as metadata on the object. All children classes must declare which
* properties are columns of the type table or they will be assumed
* to be metadata. See \ElggObject::initializeAttributes() for examples.
*
* Core supports 4 types of entities: \ElggObject, \ElggUser, \ElggGroup, and \ElggSite.
*
* @tip Plugin authors will want to extend the \ElggObject class, not this class.
*
* @property-read string $type object, user, group, or site (read-only after save)
* @property-read string $subtype Further clarifies the nature of the entity
* @property-read int $guid The unique identifier for this entity (read only)
* @property int $owner_guid The GUID of the owner of this entity (usually the creator)
* @property int $container_guid The GUID of the entity containing this entity
* @property int $access_id Specifies the visibility level of this entity
* @property int $time_created A UNIX timestamp of when the entity was created
* @property-read int $time_updated A UNIX timestamp of when the entity was last updated (automatically updated on save)
* @property-read int $last_action A UNIX timestamp of when the entity was last acted upon
* @property-read int $time_deleted A UNIX timestamp of when the entity was deleted
* @property-read string $deleted Is this entity deleted ('yes' or 'no')
* @property-read string $enabled Is this entity enabled ('yes' or 'no')
*
* Metadata (the above are attributes)
* @property string $location A location of the entity
*/
abstract class ElggEntity extends \ElggData {
use AccessCollections;
use Annotations;
use Icons;
use Metadata;
use Relationships;
use Subscriptions;
public const PRIMARY_ATTR_NAMES = [
'guid',
'type',
'subtype',
'owner_guid',
'container_guid',
'access_id',
'time_created',
'time_updated',
'last_action',
'enabled',
'deleted',
'time_deleted',
];
/**
* @var string[] attributes that are integers
*/
protected const INTEGER_ATTR_NAMES = [
'guid',
'owner_guid',
'container_guid',
'access_id',
'time_created',
'time_updated',
'last_action',
'time_deleted',
];
/**
* Volatile data structure for this object, allows for storage of data
* in-memory that isn't synced back to the metadata table.
* @var array
*/
protected $volatile = [];
/**
* Holds the original (persisted) attribute values that have been changed but not yet saved.
* @var array
*/
protected $orig_attributes = [];
/**
* @var bool
*/
protected $_is_cacheable = true;
/**
* Create a new entity.
*
* Plugin developers should only use the constructor to create a new entity.
* To retrieve entities, use get_entity() and the elgg_get_entities* functions.
*
* If no arguments are passed, it creates a new entity.
* If a database result is passed as a \stdClass instance, it instantiates
* that entity.
*
* @param null|\stdClass $row Database row result. Default is null to create a new object.
*
* @throws IOException If cannot load remaining data from db
*/
public function __construct(?\stdClass $row = null) {
$this->initializeAttributes();
if (!empty($row) && !$this->load($row)) {
throw new IOException('Failed to load new ' . get_class() . " for GUID: {$row->guid}");
}
}
/**
* Initialize the attributes array.
*
* This is vital to distinguish between metadata and base parameters.
*
* @return void
*/
protected function initializeAttributes() {
parent::initializeAttributes();
$this->attributes['guid'] = null;
$this->attributes['type'] = null;
$this->attributes['subtype'] = null;
$this->attributes['owner_guid'] = _elgg_services()->session_manager->getLoggedInUserGuid();
$this->attributes['container_guid'] = _elgg_services()->session_manager->getLoggedInUserGuid();
$this->attributes['access_id'] = ACCESS_PRIVATE;
$this->attributes['time_updated'] = null;
$this->attributes['last_action'] = null;
$this->attributes['enabled'] = 'yes';
$this->attributes['deleted'] = 'no';
$this->attributes['time_deleted'] = null;
}
/**
* Clone an entity
*
* Resets the guid so that the entity can be saved as a distinct entity from
* the original. Creation time will be set when this new entity is saved.
* The owner and container guids come from the original entity. The clone
* method copies metadata but does not copy annotations.
*
* @return void
*/
public function __clone() {
$orig_entity = get_entity($this->guid);
if (!$orig_entity) {
_elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
return;
}
$metadata_array = elgg_get_metadata([
'guid' => $this->guid,
'limit' => false,
]);
$this->attributes['guid'] = null;
$this->attributes['time_created'] = null;
$this->attributes['time_updated'] = null;
$this->attributes['last_action'] = null;
$this->attributes['subtype'] = $orig_entity->getSubtype();
// copy metadata over to new entity - slightly convoluted due to
// handling of metadata arrays
if (is_array($metadata_array)) {
// create list of metadata names
$metadata_names = [];
foreach ($metadata_array as $metadata) {
$metadata_names[] = $metadata->name;
}
// arrays are stored with multiple entries per name
$metadata_names = array_unique($metadata_names);
// move the metadata over
foreach ($metadata_names as $name) {
$this->__set($name, $orig_entity->$name);
}
}
}
/**
* Set an attribute or metadata value for this entity
*
* Anything that is not an attribute is saved as metadata.
*
* Be advised that metadata values are cast to integer or string.
* You can save booleans, but they will be stored and returned as integers.
*
* @param string $name Name of the attribute or metadata
* @param mixed $value The value to be set
*
* @return void
* @throws \Elgg\Exceptions\InvalidArgumentException
* @see \ElggEntity::setMetadata()
*/
public function __set($name, $value) {
if ($this->$name === $value) {
// quick return if value is not changing
return;
}
if (array_key_exists($name, $this->attributes)) {
// if an attribute is 1 (integer) and it's set to "1" (string), don't consider that a change.
if (is_int($this->attributes[$name])
&& is_string($value)
&& ((string) $this->attributes[$name] === $value)) {
return;
}
// keep original values
if ($this->guid && !array_key_exists($name, $this->orig_attributes)) {
$this->orig_attributes[$name] = $this->attributes[$name];
}
// Certain properties should not be manually changed!
switch ($name) {
case 'guid':
case 'last_action':
case 'time_deleted':
case 'time_updated':
case 'type':
return;
case 'subtype':
throw new ElggInvalidArgumentException(elgg_echo('ElggEntity:Error:SetSubtype', ['setSubtype()']));
case 'enabled':
throw new ElggInvalidArgumentException(elgg_echo('ElggEntity:Error:SetEnabled', ['enable() / disable()']));
case 'deleted':
throw new ElggInvalidArgumentException(elgg_echo('ElggEntity:Error:SetDeleted', ['delete() / restore()']));
case 'access_id':
case 'owner_guid':
case 'container_guid':
if ($value !== null) {
$this->attributes[$name] = (int) $value;
} else {
$this->attributes[$name] = null;
}
break;
default:
$this->attributes[$name] = $value;
break;
}
return;
}
$this->setMetadata($name, $value);
}
/**
* Get the original values of attribute(s) that have been modified since the entity was persisted.
*
* @return array
*/
public function getOriginalAttributes(): array {
return $this->orig_attributes;
}
/**
* Get an attribute or metadata value
*
* If the name matches an attribute, the attribute is returned. If metadata
* does not exist with that name, a null is returned.
*
* This only returns an array if there are multiple values for a particular
* $name key.
*
* @param string $name Name of the attribute or metadata
*
* @return mixed
*/
public function __get($name) {
if (array_key_exists($name, $this->attributes)) {
return $this->attributes[$name];
}
return $this->getMetadata($name);
}
/**
* Get the entity's display name
*
* @return string The title or name of this entity.
*/
public function getDisplayName(): string {
return (string) $this->name;
}
/**
* Sets the title or name of this entity.
*
* @param string $display_name The title or name of this entity.
*
* @return void
*/
public function setDisplayName(string $display_name): void {
$this->name = $display_name;
}
/**
* Get a piece of volatile (non-persisted) data on this entity.
*
* @param string $name The name of the volatile data
*
* @return mixed The value or null if not found.
*/
public function getVolatileData(string $name) {
return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
}
/**
* Set a piece of volatile (non-persisted) data on this entity
*
* @param string $name Name
* @param mixed $value Value
*
* @return void
*/
public function setVolatileData(string $name, $value): void {
$this->volatile[$name] = $value;
}
/**
* Removes all river items related to this entity
*
* @return void
*/
public function removeAllRelatedRiverItems(): void {
elgg_delete_river(['subject_guid' => $this->guid, 'limit' => false]);
elgg_delete_river(['object_guid' => $this->guid, 'limit' => false]);
elgg_delete_river(['target_guid' => $this->guid, 'limit' => false]);
}
/**
* Count the number of comments attached to this entity.
*
* @return int Number of comments
* @since 1.8.0
*/
public function countComments(): int {
if (!$this->hasCapability('commentable')) {
return 0;
}
$params = ['entity' => $this];
$num = _elgg_services()->events->triggerResults('comments:count', $this->getType(), $params);
if (is_int($num)) {
return $num;
}
return \Elgg\Comments\DataService::instance()->getCommentsCount($this);
}
/**
* Check if the given user has access to this entity
*
* @param int $user_guid the GUID of the user to check access for (default: logged in user_guid)
*
* @return bool
* @since 4.3
*/
public function hasAccess(int $user_guid = 0): bool {
return _elgg_services()->accessCollections->hasAccessToEntity($this, $user_guid);
}
/**
* Can a user edit this entity?
*
* @tip Can be overridden by registering for the permissions_check event.
*
* @param int $user_guid The user GUID, optionally (default: logged in user)
*
* @return bool Whether this entity is editable by the given user.
*/
public function canEdit(int $user_guid = 0): bool {
return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
}
/**
* Can a user delete this entity?
*
* @tip Can be overridden by registering for the permissions_check:delete event.
*
* @param int $user_guid The user GUID, optionally (default: logged in user)
*
* @return bool Whether this entity is deletable by the given user.
* @since 1.11
*/
public function canDelete(int $user_guid = 0): bool {
return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
}
/**
* Can a user add an entity to this container
*
* @param int $user_guid The GUID of the user creating the entity (0 for logged in user).
* @param string $type The type of entity we're looking to write
* @param string $subtype The subtype of the entity we're looking to write
*
* @return bool
* @throws \Elgg\Exceptions\InvalidArgumentException
*/
public function canWriteToContainer(int $user_guid = 0, string $type = '', string $subtype = ''): bool {
if (empty($type) || empty($subtype)) {
throw new ElggInvalidArgumentException(__METHOD__ . ' requires $type and $subtype to be set');
}
return _elgg_services()->userCapabilities->canWriteToContainer($this, $type, $subtype, $user_guid);
}
/**
* Can a user comment on an entity?
*
* @tip Can be overridden by registering for the 'permissions_check:comment', '<entity type>' event.
*
* @param int $user_guid User guid (default is logged in user)
*
* @return bool
*/
public function canComment(int $user_guid = 0): bool {
return _elgg_services()->userCapabilities->canComment($this, $user_guid);
}
/**
* Can a user annotate an entity?
*
* @tip Can be overridden by registering for the event [permissions_check:annotate:<name>,
* <entity type>] or [permissions_check:annotate, <entity type>]. The events are called in that order.
*
* @tip If you want logged out users to annotate an object, do not call
* canAnnotate(). It's easier than using the event.
*
* @param int $user_guid User guid (default is logged in user)
* @param string $annotation_name The name of the annotation (default is unspecified)
*
* @return bool
*/
public function canAnnotate(int $user_guid = 0, string $annotation_name = ''): bool {
return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
}
/**
* Returns the guid.
*
* @return int|null GUID
*/
public function getGUID(): ?int {
return $this->guid;
}
/**
* Returns the entity type
*
* @return string The entity type
*/
public function getType(): string {
return (string) $this->attributes['type'];
}
/**
* Set the subtype of the entity
*
* @param string $subtype the new type
*
* @return void
* @see self::initializeAttributes()
*/
public function setSubtype(string $subtype): void {
// keep original values
if ($this->guid && !array_key_exists('subtype', $this->orig_attributes)) {
$this->orig_attributes['subtype'] = $this->attributes['subtype'];
}
$this->attributes['subtype'] = $subtype;
}
/**
* Get the entity subtype
*
* @return string The entity subtype
*/
public function getSubtype(): string {
return (string) $this->attributes['subtype'];
}
/**
* Get the guid of the entity's owner.
*
* @return int The owner GUID
*/
public function getOwnerGUID(): int {
return (int) $this->owner_guid;
}
/**
* Gets the \ElggEntity that owns this entity.
*
* @return \ElggEntity|null
*/
public function getOwnerEntity(): ?\ElggEntity {
return $this->owner_guid ? get_entity($this->owner_guid) : null;
}
/**
* Set the container for this object.
*
* @param int $container_guid The ID of the container.
*
* @return void
*/
public function setContainerGUID(int $container_guid): void {
$this->container_guid = $container_guid;
}
/**
* Gets the container GUID for this entity.
*
* @return int
*/
public function getContainerGUID(): int {
return (int) $this->container_guid;
}
/**
* Get the container entity for this object.
*
* @return \ElggEntity|null
* @since 1.8.0
*/
public function getContainerEntity(): ?\ElggEntity {
return $this->container_guid ? get_entity($this->getContainerGUID()) : null;
}
/**
* Returns the UNIX epoch time that this entity was last updated
*
* @return int UNIX epoch time
*/
public function getTimeUpdated(): int {
return (int) $this->time_updated;
}
/**
* Gets the URL for this entity.
*
* Plugins can register for the 'entity:url', '<type>' event to
* customize the url for an entity.
*
* @return string The URL of the entity
*/
public function getURL(): string {
$url = elgg_generate_entity_url($this, 'view');
$url = _elgg_services()->events->triggerResults('entity:url', "{$this->getType()}:{$this->getSubtype()}", ['entity' => $this], $url);
$url = _elgg_services()->events->triggerResults('entity:url', $this->getType(), ['entity' => $this], $url);
if (empty($url)) {
return '';
}
return elgg_normalize_url($url);
}
/**
* {@inheritDoc}
*/
public function save(): bool {
if ($this->guid > 0) {
$result = $this->update();
} else {
$result = $this->create() !== false;
}
if ($result) {
$this->cache();
}
return $result;
}
/**
* Create a new entry in the entities table.
*
* Saves the base information in the entities table for the entity. Saving
* the type-specific information is handled in the calling class method.
*
* @return int|false The new entity's GUID or false if prevented by an event handler
*
* @throws \Elgg\Exceptions\DomainException If the entity's type has not been set.
* @throws \Elgg\Exceptions\InvalidArgumentException If the entity's subtype has not been set, access_id is invalid or something is wrong with the owner or container
* @throws \Elgg\Exceptions\Filesystem\IOException If the new row fails to write to the DB.
*/
protected function create() {
$type = $this->attributes['type'];
if (!in_array($type, \Elgg\Config::ENTITY_TYPES)) {
throw new ElggDomainException('Entity type must be one of the allowed types: ' . implode(', ', \Elgg\Config::ENTITY_TYPES));
}
$subtype = $this->attributes['subtype'];
if (!$subtype) {
throw new ElggInvalidArgumentException('All entities must have a subtype');
}
$owner_guid = (int) $this->attributes['owner_guid'];
$access_id = (int) $this->attributes['access_id'];
$now = $this->getCurrentTime()->getTimestamp();
$time_created = isset($this->attributes['time_created']) ? (int) $this->attributes['time_created'] : $now;
$deleted = $this->attributes['deleted'];
$time_deleted = (int) $this->attributes['time_deleted'];
$container_guid = $this->attributes['container_guid'];
if ($container_guid == 0) {
$container_guid = $owner_guid;
$this->attributes['container_guid'] = $container_guid;
}
$container_guid = (int) $container_guid;
if ($access_id === ACCESS_DEFAULT) {
throw new ElggInvalidArgumentException('ACCESS_DEFAULT is not a valid access level. See its documentation in constants.php');
}
if ($access_id === ACCESS_FRIENDS) {
throw new ElggInvalidArgumentException('ACCESS_FRIENDS is not a valid access level. See its documentation in constants.php');
}
$user_guid = _elgg_services()->session_manager->getLoggedInUserGuid();
// If given an owner, verify it can be loaded
if (!empty($owner_guid)) {
$owner = $this->getOwnerEntity();
if (!$owner instanceof \ElggEntity) {
$error = "User {$user_guid} tried to create a ({$type}, {$subtype}),";
$error .= " but the given owner {$owner_guid} could not be loaded.";
throw new ElggInvalidArgumentException($error);
}
// If different owner than logged in, verify can write to container.
if ($user_guid !== $owner_guid && !$owner->canEdit() && !$owner->canWriteToContainer($user_guid, $type, $subtype)) {
$error = "User {$user_guid} tried to create a ({$type}, {$subtype}) with owner {$owner_guid},";
$error .= " but the user wasn't permitted to write to the owner's container.";
throw new ElggInvalidArgumentException($error);
}
}
// If given a container, verify it can be loaded and that the current user can write to it
if (!empty($container_guid)) {
$container = $this->getContainerEntity();
if (!$container instanceof \ElggEntity) {
$error = "User {$user_guid} tried to create a ({$type}, {$subtype}),";
$error .= " but the given container {$container_guid} could not be loaded.";
throw new ElggInvalidArgumentException($error);
}
if (!$container->canWriteToContainer($user_guid, $type, $subtype)) {
$error = "User {$user_guid} tried to create a ({$type}, {$subtype}),";
$error .= " but was not permitted to write to container {$container_guid}.";
throw new ElggInvalidArgumentException($error);
}
}
if (!_elgg_services()->events->triggerBefore('create', $this->type, $this)) {
return false;
}
// Create primary table row
$guid = _elgg_services()->entityTable->insertRow((object) [
'type' => $type,
'subtype' => $subtype,
'owner_guid' => $owner_guid,
'container_guid' => $container_guid,
'access_id' => $access_id,
'time_created' => $time_created,
'time_updated' => $now,
'last_action' => $now,
'deleted' => $deleted,
'time_deleted' => $time_deleted
], $this->attributes);
if (!$guid) {
throw new IOException("Unable to save new object's base entity information!");
}
$this->attributes['subtype'] = $subtype;
$this->attributes['guid'] = (int) $guid;
$this->attributes['time_created'] = (int) $time_created;
$this->attributes['time_updated'] = (int) $now;
$this->attributes['last_action'] = (int) $now;
$this->attributes['container_guid'] = (int) $container_guid;
$this->attributes['deleted'] = $deleted;
$this->attributes['time_deleted'] = (int) $time_deleted;
// We are writing this new entity to cache to make sure subsequent calls
// to get_entity() load the entity from cache and not from the DB. This
// MUST come before the metadata and annotation writes below!
$this->cache();
// Save any unsaved metadata
if (count($this->temp_metadata) > 0) {
foreach ($this->temp_metadata as $name => $value) {
// temp metadata is always an array, but if there is only one value return just the value
$this->setMetadata($name, $value, '', count($value) > 1);
}
$this->temp_metadata = [];
}
// Save any unsaved annotations.
if (count($this->temp_annotations) > 0) {
foreach ($this->temp_annotations as $name => $value) {
$this->annotate($name, $value);
}
$this->temp_annotations = [];
}
if (isset($container) && !$container instanceof \ElggUser) {
// users have their own logic for setting last action
$container->updateLastAction($this->time_created);
}
// for BC reasons this event is still needed (for example for notifications)
_elgg_services()->events->trigger('create', $this->type, $this);
_elgg_services()->events->triggerAfter('create', $this->type, $this);
return $guid;
}
/**
* Update the entity in the database.
*
* @return bool Whether the update was successful.
*
* @throws \Elgg\Exceptions\InvalidArgumentException
*/
protected function update(): bool {
if (!$this->canEdit()) {
return false;
}
// give old update event a chance to stop the update
if (!_elgg_services()->events->trigger('update', $this->type, $this)) {
return false;
}
$this->invalidateCache();
// See #6225. We copy these after the update event in case a handler changed one of them.
$guid = (int) $this->guid;
$owner_guid = (int) $this->owner_guid;
$access_id = (int) $this->access_id;
$container_guid = (int) $this->container_guid;
$time_created = (int) $this->time_created;
$time = $this->getCurrentTime()->getTimestamp();
$deleted = $this->deleted;
$time_deleted = (int) $this->time_deleted;
if ($access_id == ACCESS_DEFAULT) {
throw new ElggInvalidArgumentException('ACCESS_DEFAULT is not a valid access level. See its documentation in constants.php');
}
if ($access_id == ACCESS_FRIENDS) {
throw new ElggInvalidArgumentException('ACCESS_FRIENDS is not a valid access level. See its documentation in constants.php');
}
// Update primary table
$ret = _elgg_services()->entityTable->updateRow($guid, (object) [
'owner_guid' => $owner_guid,
'container_guid' => $container_guid,
'access_id' => $access_id,
'time_created' => $time_created,
'time_updated' => $time,
'guid' => $guid,
'deleted' => $deleted,
'time_deleted' => $time_deleted
]);
if ($ret === false) {
return false;
}
$this->attributes['time_updated'] = $time;
_elgg_services()->events->triggerAfter('update', $this->type, $this);
$this->orig_attributes = [];
$this->cache();
// Handle cases where there was no error BUT no rows were updated!
return true;
}
/**
* Loads attributes from the entities table into the object.
*
* @param stdClass $row Object of properties from database row(s)
*
* @return bool
*/
protected function load(stdClass $row): bool {
$attributes = array_merge($this->attributes, (array) $row);
if (array_diff(self::PRIMARY_ATTR_NAMES, array_keys($attributes)) !== []) {
// Some primary attributes are missing
return false;
}
foreach ($attributes as $name => $value) {
if (!in_array($name, self::PRIMARY_ATTR_NAMES)) {
$this->setVolatileData("select:{$name}", $value);
unset($attributes[$name]);
continue;
}
if (in_array($name, static::INTEGER_ATTR_NAMES)) {
$attributes[$name] = (int) $value;
}
}
$this->attributes = $attributes;
$this->cache();
return true;
}
/**
* Disable this entity.
*
* Disabled entities are not returned by getter functions.
* To enable an entity, use {@link \ElggEntity::enable()}.
*
* Recursively disabling an entity will disable all entities
* owned or contained by the parent entity.
*
* @note Internal: Disabling an entity sets the 'enabled' column to 'no'.
*
* @param string $reason Optional reason
* @param bool $recursive Recursively disable all contained entities?
*
* @return bool
* @see \ElggEntity::enable()
*/
public function disable(string $reason = '', bool $recursive = true): bool {
if (!$this->guid) {
return false;
}
if (!_elgg_services()->events->trigger('disable', $this->type, $this)) {
return false;
}
if (!$this->canEdit()) {
return false;
}
if ($this instanceof ElggUser && !$this->isBanned()) {
// temporarily ban to prevent using the site during disable
// not using ban function to bypass events
$this->setMetadata('banned', 'yes');
$unban_after = true;
} else {
$unban_after = false;
}
if (!empty($reason)) {
$this->disable_reason = $reason;
}
$guid = (int) $this->guid;
if ($recursive) {
elgg_call(ELGG_IGNORE_ACCESS | ELGG_HIDE_DISABLED_ENTITIES | ELGG_SHOW_DELETED_ENTITIES, function () use ($guid, $reason) {
$base_options = [
'wheres' => [
function(QueryBuilder $qb, $main_alias) use ($guid) {
return $qb->compare("{$main_alias}.guid", '!=', $guid, ELGG_VALUE_GUID);
},
],
'limit' => false,
'batch' => true,
'batch_inc_offset' => false,
];
foreach (['owner_guid', 'container_guid'] as $db_column) {
$options = $base_options;
$options[$db_column] = $guid;
$subentities = elgg_get_entities($options);
/* @var $subentity \ElggEntity */
foreach ($subentities as $subentity) {
if (!$subentity->isEnabled()) {
continue;
}
$subentity->addRelationship($guid, 'disabled_with');
$subentity->disable($reason, true);
}
}
});
}
$disabled = _elgg_services()->entityTable->disable($this);
if ($unban_after) {
$this->setMetadata('banned', 'no');
}
if ($disabled) {
$this->invalidateCache();
$this->attributes['enabled'] = 'no';
_elgg_services()->events->triggerAfter('disable', $this->type, $this);
}
return $disabled;
}
/**
* Enable the entity
*
* @param bool $recursive Recursively enable all entities disabled with the entity?
*
* @return bool
*/
public function enable(bool $recursive = true): bool {
if (empty($this->guid)) {
return false;
}
if (!_elgg_services()->events->trigger('enable', $this->type, $this)) {
return false;
}
if (!$this->canEdit()) {
return false;
}
$result = elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES | ELGG_SHOW_DELETED_ENTITIES, function() use ($recursive) {
$result = _elgg_services()->entityTable->enable($this);
$this->deleteMetadata('disable_reason');
if ($recursive) {
$disabled_with_it = elgg_get_entities([
'relationship' => 'disabled_with',
'relationship_guid' => $this->guid,
'inverse_relationship' => true,
'limit' => false,
'batch' => true,
'batch_inc_offset' => false,
]);
foreach ($disabled_with_it as $e) {
$e->enable($recursive);
$e->removeRelationship($this->guid, 'disabled_with');
}
}
return $result;
});
if ($result) {
$this->attributes['enabled'] = 'yes';
_elgg_services()->events->triggerAfter('enable', $this->type, $this);
}
return $result;
}
/**
* Is this entity enabled?
*
* @return boolean Whether this entity is enabled.
*/
public function isEnabled(): bool {
return $this->enabled == 'yes';
}
/**
* Deletes the entity.
*
* Removes the entity and its metadata, annotations, relationships,
* river entries, and private data.
*
* Optionally can remove entities contained and owned by this entity.
*
* @warning If deleting recursively, this bypasses ownership of items contained by
* the entity. That means that if the container_guid = $this->guid, the item will
* be deleted regardless of who owns it.
*
* @param bool $recursive If true (default) then all entities which are owned or contained by $this will also be deleted.
* @param bool|null $persistent persistently delete the entity (default: check the 'restorable' capability)
*
* @return bool
*/
public function delete(bool $recursive = true, ?bool $persistent = null): bool {
if (!$this->canDelete()) {
return false;
}
if (!elgg_get_config('trash_enabled')) {
$persistent = true;
}
if (!isset($persistent)) {
$persistent = !$this->hasCapability('restorable');
}
try {
if (empty($this->guid) || $persistent) {
return $this->persistentDelete($recursive);
} else {
return $this->trash($recursive);
}
} catch (DatabaseException $ex) {
elgg_log($ex, \Psr\Log\LogLevel::ERROR);
return false;
}
}
/**
* Permanently delete the entity from the database
*
* @param bool $recursive If true (default) then all entities which are owned or contained by $this will also be deleted.
*
* @return bool
* @since 6.0
*/
protected function persistentDelete(bool $recursive = true): bool {
return _elgg_services()->entityTable->delete($this, $recursive);
}
/**
* Move the entity to the trash
*
* @param bool $recursive If true (default) then all entities which are owned or contained by $this will also be trashed.
*
* @return bool
* @since 6.0
*/
protected function trash(bool $recursive = true): bool {
$result = _elgg_services()->entityTable->trash($this, $recursive);
if ($result) {
$this->attributes['deleted'] = 'yes';
}
return $result;
}
/**
* Restore the entity
*
* @param bool $recursive Recursively restores all entities trashed with the entity?
*
* @return bool
* @since 6.0
*/
public function restore(bool $recursive = true): bool {
if (!$this->isDeleted()) {
return true;
}
if (empty($this->guid) || !$this->canEdit()) {
return false;
}
return _elgg_services()->events->triggerSequence('restore', $this->type, $this, function () use ($recursive) {
return elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES | ELGG_SHOW_DELETED_ENTITIES, function() use ($recursive) {
if (!_elgg_services()->entityTable->restore($this)) {
return false;
}
$this->attributes['deleted'] = 'no';
$this->attributes['time_deleted'] = 0;
$this->removeAllRelationships('deleted_by');
$this->removeAllRelationships('deleted_with');
if (!$recursive) {
return true;
}
set_time_limit(0);
/* @var $deleted_with_it \ElggBatch */
$deleted_with_it = elgg_get_entities([
'relationship' => 'deleted_with',
'relationship_guid' => $this->guid,
'inverse_relationship' => true,
'limit' => false,
'batch' => true,
'batch_inc_offset' => false,
]);
/* @var $e \ElggEntity */
foreach ($deleted_with_it as $e) {
if (!$e->restore($recursive)) {
$deleted_with_it->reportFailure();
continue;
}
}
return true;
});
});
}
/**
* Is the entity marked as deleted
*
* @return bool
*/
public function isDeleted(): bool {
return $this->deleted === 'yes';
}
/**
* Export an entity
*
* @param array $params Params to pass to the event
*
* @return \Elgg\Export\Entity
*/
public function toObject(array $params = []) {
$object = $this->prepareObject(new \Elgg\Export\Entity());
$params['entity'] = $this;
return _elgg_services()->events->triggerResults('to:object', 'entity', $params, $object);
}
/**
* Prepare an object copy for toObject()
*
* @param \Elgg\Export\Entity $object Object representation of the entity
*
* @return \Elgg\Export\Entity
*/
protected function prepareObject(\Elgg\Export\Entity $object) {
$object->guid = $this->guid;
$object->type = $this->getType();
$object->subtype = $this->getSubtype();
$object->owner_guid = $this->getOwnerGUID();
$object->container_guid = $this->getContainerGUID();
$object->time_created = date('c', $this->getTimeCreated());
$object->time_updated = date('c', $this->getTimeUpdated());
$object->url = $this->getURL();
$object->read_access = (int) $this->access_id;
return $object;
}
/**
* Set latitude and longitude metadata tags for a given entity.
*
* @param float $lat Latitude
* @param float $long Longitude
*
* @return void
*/
public function setLatLong(float $lat, float $long): void {
$this->{'geo:lat'} = $lat;
$this->{'geo:long'} = $long;
}
/**
* Return the entity's latitude.
*
* @return float
*/
public function getLatitude(): float {
return (float) $this->{'geo:lat'};
}
/**
* Return the entity's longitude
*
* @return float
*/
public function getLongitude(): float {
return (float) $this->{'geo:long'};
}
/*
* SYSTEM LOG INTERFACE
*/
/**
* {@inheritdoc}
*/
public function getSystemLogID(): int {
return (int) $this->getGUID();
}
/**
* For a given ID, return the object associated with it.
* This is used by the system log. It can be called on any Loggable object.
*
* @param int $id GUID
*
* @return \ElggEntity|null
*/
public function getObjectFromID(int $id): ?\ElggEntity {
return get_entity($id);
}
/**
* Update the last_action column in the entities table.
*
* @warning This is different to time_updated. Time_updated is automatically set,
* while last_action is only set when explicitly called.
*
* @param null|int $posted Timestamp of last action
*
* @return int
* @internal
*/
public function updateLastAction(?int $posted = null): int {
$posted = _elgg_services()->entityTable->updateLastAction($this, $posted);
$this->attributes['last_action'] = $posted;
$this->cache();
return $posted;
}
/**
* Update the time_deleted column in the entities table.
*
* @param null|int $deleted Timestamp of deletion
*
* @return int
* @internal
*/
public function updateTimeDeleted(?int $deleted = null): int {
$deleted = _elgg_services()->entityTable->updateTimeDeleted($this, $deleted);
$this->attributes['time_deleted'] = $deleted;
$this->cache();
return $deleted;
}
/**
* Disable runtime caching for entity
*
* @return void
* @internal
*/
public function disableCaching(): void {
$this->_is_cacheable = false;
if ($this->guid) {
_elgg_services()->entityCache->delete($this->guid);
}
}
/**
* Enable runtime caching for entity
*
* @return void
* @internal
*/
public function enableCaching(): void {
$this->_is_cacheable = true;
}
/**
* Is entity cacheable in the runtime cache
*
* @return bool
* @internal
*/
public function isCacheable(): bool {
if (!$this->guid) {
return false;
}
if (_elgg_services()->session_manager->getIgnoreAccess()) {
return false;
}
return $this->_is_cacheable;
}
/**
* Cache the entity in a session cache
*
* @return void
* @internal
*/
public function cache(): void {
if (!$this->isCacheable()) {
return;
}
_elgg_services()->entityCache->save($this->guid, $this);
}
/**
* Invalidate cache for entity
*
* @return void
* @internal
*/
public function invalidateCache(): void {
if (!$this->guid) {
return;
}
_elgg_services()->entityCache->delete($this->guid);
_elgg_services()->metadataCache->delete($this->guid);
}
/**
* Checks a specific capability is enabled for the entity type/subtype
*
* @param string $capability capability to check
*
* @return bool
* @since 4.1
*/
public function hasCapability(string $capability): bool {
return _elgg_services()->entity_capabilities->hasCapability($this->getType(), $this->getSubtype(), $capability);
}
/**
* Returns a default set of fields to be used for forms related to this entity
*
* @return array
*/
public static function getDefaultFields(): array {
return [];
}
/**
* Helper function to easily retrieve form fields for this entity
*
* @return array
*/
final public function getFields(): array {
return _elgg_services()->fields->get($this->getType(), $this->getSubtype());
}
}