diff options
Diffstat (limited to 'engine/classes/ElggEntity.php')
-rw-r--r-- | engine/classes/ElggEntity.php | 1770 |
1 files changed, 1770 insertions, 0 deletions
diff --git a/engine/classes/ElggEntity.php b/engine/classes/ElggEntity.php new file mode 100644 index 000000000..a563f6fad --- /dev/null +++ b/engine/classes/ElggEntity.php @@ -0,0 +1,1770 @@ +<?php +/** + * 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::initialise_entities() for examples. + * + * Core supports 4 types of entities: ElggObject, ElggUser, ElggGroup, and + * ElggSite. + * + * @tip Most plugin authors will want to extend the ElggObject class + * instead of this class. + * + * @package Elgg.Core + * @subpackage DataModel.Entities + * + * @property string $type object, user, group, or site (read-only after save) + * @property string $subtype Further clarifies the nature of the entity (read-only after save) + * @property int $guid The unique identifier for this entity (read only) + * @property int $owner_guid The GUID of the creator of this entity + * @property int $container_guid The GUID of the entity containing this entity + * @property int $site_guid The GUID of the website this entity is associated with + * @property int $access_id Specifies the visibility level of this entity + * @property int $time_created A UNIX timestamp of when the entity was created (read-only, set on first save) + * @property int $time_updated A UNIX timestamp of when the entity was last updated (automatically updated on save) + * @property-read string $enabled + */ +abstract class ElggEntity extends ElggData implements + Notable, // Calendar interface + Locatable, // Geocoding interface + Importable // Allow import of data +{ + + /** + * If set, overrides the value of getURL() + */ + protected $url_override; + + /** + * Icon override, overrides the value of getIcon(). + */ + protected $icon_override; + + /** + * Holds metadata until entity is saved. Once the entity is saved, + * metadata are written immediately to the database. + */ + protected $temp_metadata = array(); + + /** + * Holds annotations until entity is saved. Once the entity is saved, + * annotations are written immediately to the database. + */ + protected $temp_annotations = array(); + + /** + * Holds private settings until entity is saved. Once the entity is saved, + * private settings are written immediately to the database. + */ + protected $temp_private_settings = array(); + + /** + * Volatile data structure for this object, allows for storage of data + * in-memory that isn't sync'd back to the metadata table. + */ + protected $volatile = array(); + + /** + * 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_get_logged_in_user_guid(); + $this->attributes['container_guid'] = elgg_get_logged_in_user_guid(); + + $this->attributes['site_guid'] = NULL; + $this->attributes['access_id'] = ACCESS_PRIVATE; + $this->attributes['time_created'] = NULL; + $this->attributes['time_updated'] = NULL; + $this->attributes['last_action'] = NULL; + $this->attributes['enabled'] = "yes"; + + // There now follows a bit of a hack + /* Problem: To speed things up, some objects are split over several tables, + * this means that it requires n number of database reads to fully populate + * an entity. This causes problems for caching and create events + * since it is not possible to tell whether a subclassed entity is complete. + * + * Solution: We have two counters, one 'tables_split' which tells whatever is + * interested how many tables are going to need to be searched in order to fully + * populate this object, and 'tables_loaded' which is how many have been + * loaded thus far. + * + * If the two are the same then this object is complete. + * + * Use: isFullyLoaded() to check + */ + $this->attributes['tables_split'] = 1; + $this->attributes['tables_loaded'] = 0; + } + + /** + * 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 or private settings. + * + * @note metadata will have its owner and access id set when the entity is saved + * and it will be the same as that of the entity. + * + * @return void + */ + public function __clone() { + $orig_entity = get_entity($this->guid); + if (!$orig_entity) { + elgg_log("Failed to clone entity with GUID $this->guid", "ERROR"); + return; + } + + $metadata_array = elgg_get_metadata(array( + 'guid' => $this->guid, + 'limit' => 0 + )); + + $this->attributes['guid'] = ""; + + $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 = array(); + foreach ($metadata_array as $metadata) { + $metadata_names[] = $metadata['name']; + } + // arrays are stored with multiple enties per name + $metadata_names = array_unique($metadata_names); + + // move the metadata over + foreach ($metadata_names as $name) { + $this->set($name, $orig_entity->$name); + } + } + } + + /** + * Return the value of a property. + * + * If $name is defined in $this->attributes that value is returned, otherwise it will + * pull from the entity's metadata. + * + * Q: Why are we not using __get overload here? + * A: Because overload operators cause problems during subclassing, so we put the code here and + * create overloads in subclasses. + * + * @todo What problems are these? + * + * @warning Subtype is returned as an id rather than the subtype string. Use getSubtype() + * to get the subtype string. + * + * @param string $name Name + * + * @return mixed Returns the value of a given value, or null. + */ + public function get($name) { + // See if its in our base attributes + if (array_key_exists($name, $this->attributes)) { + return $this->attributes[$name]; + } + + // No, so see if its in the meta data for this entity + $meta = $this->getMetaData($name); + + // getMetaData returns NULL if $name is not found + return $meta; + } + + /** + * Sets the value of a property. + * + * If $name is defined in $this->attributes that value is set, otherwise it is + * saved as metadata. + * + * @warning Metadata set this way will inherit the entity's owner and access ID. If you want + * to set metadata with a different owner, use create_metadata(). + * + * @warning It is important that your class populates $this->attributes with keys + * for all base attributes, anything not in their gets set as METADATA. + * + * Q: Why are we not using __set overload here? + * A: Because overload operators cause problems during subclassing, so we put the code here and + * create overloads in subclasses. + * + * @todo What problems? + * + * @param string $name Name + * @param mixed $value Value + * + * @return bool + */ + public function set($name, $value) { + if (array_key_exists($name, $this->attributes)) { + // Certain properties should not be manually changed! + switch ($name) { + case 'guid': + case 'time_updated': + case 'last_action': + return FALSE; + break; + default: + $this->attributes[$name] = $value; + break; + } + } else { + return $this->setMetaData($name, $value); + } + + return TRUE; + } + + /** + * Return the value of a piece of metadata. + * + * @param string $name Name + * + * @return mixed The value, or NULL if not found. + */ + public function getMetaData($name) { + $guid = $this->getGUID(); + + if (! $guid) { + if (isset($this->temp_metadata[$name])) { + // md is returned as an array only if more than 1 entry + if (count($this->temp_metadata[$name]) == 1) { + return $this->temp_metadata[$name][0]; + } else { + return $this->temp_metadata[$name]; + } + } else { + return null; + } + } + + // upon first cache miss, just load/cache all the metadata and retry. + // if this works, the rest of this function may not be needed! + $cache = elgg_get_metadata_cache(); + if ($cache->isKnown($guid, $name)) { + return $cache->load($guid, $name); + } else { + $cache->populateFromEntities(array($guid)); + // in case ignore_access was on, we have to check again... + if ($cache->isKnown($guid, $name)) { + return $cache->load($guid, $name); + } + } + + $md = elgg_get_metadata(array( + 'guid' => $guid, + 'metadata_name' => $name, + 'limit' => 0, + )); + + $value = null; + + if ($md && !is_array($md)) { + $value = $md->value; + } elseif (count($md) == 1) { + $value = $md[0]->value; + } else if ($md && is_array($md)) { + $value = metadata_array_to_values($md); + } + + $cache->save($guid, $name, $value); + + return $value; + } + + /** + * Unset a property from metadata or attribute. + * + * @warning If you use this to unset an attribute, you must save the object! + * + * @param string $name The name of the attribute or metadata. + * + * @return void + */ + function __unset($name) { + if (array_key_exists($name, $this->attributes)) { + $this->attributes[$name] = ""; + } else { + $this->deleteMetadata($name); + } + } + + /** + * Set a piece of metadata. + * + * Plugin authors should use the magic methods or create_metadata(). + * + * @warning The metadata will inherit the parent entity's owner and access ID. + * If you want to write metadata with a different owner, use create_metadata(). + * + * @access private + * + * @param string $name Name of the metadata + * @param mixed $value Value of the metadata (doesn't support assoc arrays) + * @param string $value_type Types supported: integer and string. Will auto-identify if not set + * @param bool $multiple Allow multiple values for a single name (doesn't support assoc arrays) + * + * @return bool + */ + public function setMetaData($name, $value, $value_type = null, $multiple = false) { + + // normalize value to an array that we will loop over + // remove indexes if value already an array. + if (is_array($value)) { + $value = array_values($value); + } else { + $value = array($value); + } + + // saved entity. persist md to db. + if ($this->guid) { + // if overwriting, delete first. + if (!$multiple) { + $options = array( + 'guid' => $this->getGUID(), + 'metadata_name' => $name, + 'limit' => 0 + ); + // @todo in 1.9 make this return false if can't add metadata + // https://github.com/elgg/elgg/issues/4520 + // + // need to remove access restrictions right now to delete + // because this is the expected behavior + $ia = elgg_set_ignore_access(true); + if (false === elgg_delete_metadata($options)) { + return false; + } + elgg_set_ignore_access($ia); + } + + // add new md + $result = true; + foreach ($value as $value_tmp) { + // at this point $value should be appended because it was cleared above if needed. + $md_id = create_metadata($this->getGUID(), $name, $value_tmp, $value_type, + $this->getOwnerGUID(), $this->getAccessId(), true); + if (!$md_id) { + return false; + } + } + + return $result; + } else { + // unsaved entity. store in temp array + // returning single entries instead of an array of 1 element is decided in + // getMetaData(), just like pulling from the db. + // + // if overwrite, delete first + if (!$multiple || !isset($this->temp_metadata[$name])) { + $this->temp_metadata[$name] = array(); + } + + // add new md + $this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value); + return true; + } + } + + /** + * Deletes all metadata on this object (metadata.entity_guid = $this->guid). + * If you pass a name, only metadata matching that name will be deleted. + * + * @warning Calling this with no $name will clear all metadata on the entity. + * + * @param null|string $name The name of the metadata to remove. + * @return bool + * @since 1.8 + */ + public function deleteMetadata($name = null) { + + if (!$this->guid) { + return false; + } + + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['metadata_name'] = $name; + } + + return elgg_delete_metadata($options); + } + + /** + * Deletes all metadata owned by this object (metadata.owner_guid = $this->guid). + * If you pass a name, only metadata matching that name will be deleted. + * + * @param null|string $name The name of metadata to delete. + * @return bool + * @since 1.8 + */ + public function deleteOwnedMetadata($name = null) { + // access is turned off for this because they might + // no longer have access to an entity they created metadata on. + $ia = elgg_set_ignore_access(true); + $options = array( + 'metadata_owner_guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['metadata_name'] = $name; + } + + $r = elgg_delete_metadata($options); + elgg_set_ignore_access($ia); + return $r; + } + + /** + * Remove metadata + * + * @warning Calling this with no or empty arguments will clear all metadata on the entity. + * + * @param string $name The name of the metadata to clear + * @return mixed bool + * @deprecated 1.8 Use deleteMetadata() + */ + public function clearMetaData($name = '') { + elgg_deprecated_notice('ElggEntity->clearMetadata() is deprecated by ->deleteMetadata()', 1.8); + return $this->deleteMetadata($name); + } + + /** + * Disables metadata for this entity, optionally based on name. + * + * @param string $name An options name of metadata to disable. + * @return bool + * @since 1.8 + */ + public function disableMetadata($name = '') { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['metadata_name'] = $name; + } + + return elgg_disable_metadata($options); + } + + /** + * Enables metadata for this entity, optionally based on name. + * + * @warning Before calling this, you must use {@link access_show_hidden_entities()} + * + * @param string $name An options name of metadata to enable. + * @return bool + * @since 1.8 + */ + public function enableMetadata($name = '') { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['metadata_name'] = $name; + } + + return elgg_enable_metadata($options); + } + + /** + * 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($name) { + if (!is_array($this->volatile)) { + $this->volatile = array(); + } + + if (array_key_exists($name, $this->volatile)) { + return $this->volatile[$name]; + } else { + return 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($name, $value) { + if (!is_array($this->volatile)) { + $this->volatile = array(); + } + + $this->volatile[$name] = $value; + } + + /** + * Remove all relationships to and from this entity. + * + * @return true + * @todo This should actually return if it worked. + * @see ElggEntity::addRelationship() + * @see ElggEntity::removeRelationship() + */ + public function deleteRelationships() { + remove_entity_relationships($this->getGUID()); + remove_entity_relationships($this->getGUID(), "", true); + return true; + } + + /** + * Remove all relationships to and from this entity. + * + * @return bool + * @see ElggEntity::addRelationship() + * @see ElggEntity::removeRelationship() + * @deprecated 1.8 Use ->deleteRelationship() + */ + public function clearRelationships() { + elgg_deprecated_notice('ElggEntity->clearRelationships() is deprecated by ->deleteRelationships()', 1.8); + return $this->deleteRelationships(); + } + + /** + * Add a relationship between this an another entity. + * + * @tip Read the relationship like "$guid is a $relationship of this entity." + * + * @param int $guid Entity to link to. + * @param string $relationship The type of relationship. + * + * @return bool + * @see ElggEntity::removeRelationship() + * @see ElggEntity::clearRelationships() + */ + public function addRelationship($guid, $relationship) { + return add_entity_relationship($this->getGUID(), $relationship, $guid); + } + + /** + * Remove a relationship + * + * @param int $guid GUID of the entity to make a relationship with + * @param str $relationship Name of relationship + * + * @return bool + * @see ElggEntity::addRelationship() + * @see ElggEntity::clearRelationships() + */ + public function removeRelationship($guid, $relationship) { + return remove_entity_relationship($this->getGUID(), $relationship, $guid); + } + + /** + * Adds a private setting to this entity. + * + * Private settings are similar to metadata but will not + * be searched and there are fewer helper functions for them. + * + * @param string $name Name of private setting + * @param mixed $value Value of private setting + * + * @return bool + */ + function setPrivateSetting($name, $value) { + if ((int) $this->guid > 0) { + return set_private_setting($this->getGUID(), $name, $value); + } else { + $this->temp_private_settings[$name] = $value; + return true; + } + } + + /** + * Returns a private setting value + * + * @param string $name Name of the private setting + * + * @return mixed + */ + function getPrivateSetting($name) { + if ((int) ($this->guid) > 0) { + return get_private_setting($this->getGUID(), $name); + } else { + if (isset($this->temp_private_settings[$name])) { + return $this->temp_private_settings[$name]; + } + } + return null; + } + + /** + * Removes private setting + * + * @param string $name Name of the private setting + * + * @return bool + */ + function removePrivateSetting($name) { + return remove_private_setting($this->getGUID(), $name); + } + + /** + * Deletes all annotations on this object (annotations.entity_guid = $this->guid). + * If you pass a name, only annotations matching that name will be deleted. + * + * @warning Calling this with no or empty arguments will clear all annotations on the entity. + * + * @param null|string $name The annotations name to remove. + * @return bool + * @since 1.8 + */ + public function deleteAnnotations($name = null) { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['annotation_name'] = $name; + } + + return elgg_delete_annotations($options); + } + + /** + * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid). + * If you pass a name, only annotations matching that name will be deleted. + * + * @param null|string $name The name of annotations to delete. + * @return bool + * @since 1.8 + */ + public function deleteOwnedAnnotations($name = null) { + // access is turned off for this because they might + // no longer have access to an entity they created annotations on. + $ia = elgg_set_ignore_access(true); + $options = array( + 'annotation_owner_guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['annotation_name'] = $name; + } + + $r = elgg_delete_annotations($options); + elgg_set_ignore_access($ia); + return $r; + } + + /** + * Disables annotations for this entity, optionally based on name. + * + * @param string $name An options name of annotations to disable. + * @return bool + * @since 1.8 + */ + public function disableAnnotations($name = '') { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['annotation_name'] = $name; + } + + return elgg_disable_annotations($options); + } + + /** + * Enables annotations for this entity, optionally based on name. + * + * @warning Before calling this, you must use {@link access_show_hidden_entities()} + * + * @param string $name An options name of annotations to enable. + * @return bool + * @since 1.8 + */ + public function enableAnnotations($name = '') { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['annotation_name'] = $name; + } + + return elgg_enable_annotations($options); + } + + /** + * Helper function to return annotation calculation results + * + * @param string $name The annotation name. + * @param string $calculation A valid MySQL function to run its values through + * @return mixed + */ + private function getAnnotationCalculation($name, $calculation) { + $options = array( + 'guid' => $this->getGUID(), + 'annotation_name' => $name, + 'annotation_calculation' => $calculation + ); + + return elgg_get_annotations($options); + } + + /** + * Adds an annotation to an entity. + * + * @warning By default, annotations are private. + * + * @warning Annotating an unsaved entity more than once with the same name + * will only save the last annotation. + * + * @param string $name Annotation name + * @param mixed $value Annotation value + * @param int $access_id Access ID + * @param int $owner_id GUID of the annotation owner + * @param string $vartype The type of annotation value + * + * @return bool + */ + function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_id = 0, $vartype = "") { + if ((int) $this->guid > 0) { + return create_annotation($this->getGUID(), $name, $value, $vartype, $owner_id, $access_id); + } else { + $this->temp_annotations[$name] = $value; + } + return true; + } + + /** + * Returns an array of annotations. + * + * @param string $name Annotation name + * @param int $limit Limit + * @param int $offset Offset + * @param string $order Order by time: asc or desc + * + * @return array + */ + function getAnnotations($name, $limit = 50, $offset = 0, $order = "asc") { + if ((int) ($this->guid) > 0) { + + $options = array( + 'guid' => $this->guid, + 'annotation_name' => $name, + 'limit' => $limit, + 'offset' => $offset, + ); + + if ($order != 'asc') { + $options['reverse_order_by'] = true; + } + + return elgg_get_annotations($options); + } else if (isset($this->temp_annotations[$name])) { + return array($this->temp_annotations[$name]); + } else { + return array(); + } + } + + /** + * Remove an annotation or all annotations for this entity. + * + * @warning Calling this method with no or an empty argument will remove + * all annotations on the entity. + * + * @param string $name Annotation name + * @return bool + * @deprecated 1.8 Use ->deleteAnnotations() + */ + function clearAnnotations($name = "") { + elgg_deprecated_notice('ElggEntity->clearAnnotations() is deprecated by ->deleteAnnotations()', 1.8); + return $this->deleteAnnotations($name); + } + + /** + * Count annotations. + * + * @param string $name The type of annotation. + * + * @return int + */ + function countAnnotations($name = "") { + return $this->getAnnotationCalculation($name, 'count'); + } + + /** + * Get the average of an integer type annotation. + * + * @param string $name Annotation name + * + * @return int + */ + function getAnnotationsAvg($name) { + return $this->getAnnotationCalculation($name, 'avg'); + } + + /** + * Get the sum of integer type annotations of a given name. + * + * @param string $name Annotation name + * + * @return int + */ + function getAnnotationsSum($name) { + return $this->getAnnotationCalculation($name, 'sum'); + } + + /** + * Get the minimum of integer type annotations of given name. + * + * @param string $name Annotation name + * + * @return int + */ + function getAnnotationsMin($name) { + return $this->getAnnotationCalculation($name, 'min'); + } + + /** + * Get the maximum of integer type annotations of a given name. + * + * @param string $name Annotation name + * + * @return int + */ + function getAnnotationsMax($name) { + return $this->getAnnotationCalculation($name, 'max'); + } + + /** + * Count the number of comments attached to this entity. + * + * @return int Number of comments + * @since 1.8.0 + */ + function countComments() { + $params = array('entity' => $this); + $num = elgg_trigger_plugin_hook('comments:count', $this->getType(), $params); + + if (is_int($num)) { + return $num; + } else { + return $this->getAnnotationCalculation('generic_comment', 'count'); + } + } + + /** + * Gets an array of entities with a relationship to this entity. + * + * @param string $relationship Relationship type (eg "friends") + * @param bool $inverse Is this an inverse relationship? + * @param int $limit Number of elements to return + * @param int $offset Indexing offset + * + * @return array|false An array of entities or false on failure + */ + function getEntitiesFromRelationship($relationship, $inverse = false, $limit = 50, $offset = 0) { + return elgg_get_entities_from_relationship(array( + 'relationship' => $relationship, + 'relationship_guid' => $this->getGUID(), + 'inverse_relationship' => $inverse, + 'limit' => $limit, + 'offset' => $offset + )); + } + + /** + * Gets the number of of entities from a specific relationship type + * + * @param string $relationship Relationship type (eg "friends") + * @param bool $inverse_relationship Invert relationship + * + * @return int|false The number of entities or false on failure + */ + function countEntitiesFromRelationship($relationship, $inverse_relationship = FALSE) { + return elgg_get_entities_from_relationship(array( + 'relationship' => $relationship, + 'relationship_guid' => $this->getGUID(), + 'inverse_relationship' => $inverse_relationship, + 'count' => TRUE + )); + } + + /** + * Can a user edit this entity. + * + * @param int $user_guid The user GUID, optionally (default: logged in user) + * + * @return bool + */ + function canEdit($user_guid = 0) { + return can_edit_entity($this->getGUID(), $user_guid); + } + + /** + * Can a user edit metadata on this entity + * + * @param ElggMetadata $metadata The piece of metadata to specifically check + * @param int $user_guid The user GUID, optionally (default: logged in user) + * + * @return bool + */ + function canEditMetadata($metadata = null, $user_guid = 0) { + return can_edit_entity_metadata($this->getGUID(), $user_guid, $metadata); + } + + /** + * Can a user add an entity to this container + * + * @param int $user_guid The 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 + */ + public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') { + return can_write_to_container($user_guid, $this->guid, $type, $subtype); + } + + /** + * Can a user comment on an entity? + * + * @tip Can be overridden by registering for the permissions_check:comment, + * <entity type> plugin hook. + * + * @param int $user_guid User guid (default is logged in user) + * + * @return bool + */ + public function canComment($user_guid = 0) { + if ($user_guid == 0) { + $user_guid = elgg_get_logged_in_user_guid(); + } + $user = get_entity($user_guid); + + // By default, we don't take a position of whether commenting is allowed + // because it is handled by the subclasses of ElggEntity + $params = array('entity' => $this, 'user' => $user); + return elgg_trigger_plugin_hook('permissions_check:comment', $this->type, $params, null); + } + + /** + * Can a user annotate an entity? + * + * @tip Can be overridden by registering for the permissions_check:annotate, + * <entity type> plugin hook. + * + * @tip If you want logged out users to annotate an object, do not call + * canAnnotate(). It's easier than using the plugin hook. + * + * @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($user_guid = 0, $annotation_name = '') { + if ($user_guid == 0) { + $user_guid = elgg_get_logged_in_user_guid(); + } + $user = get_entity($user_guid); + + $return = true; + if (!$user) { + $return = false; + } + + $params = array( + 'entity' => $this, + 'user' => $user, + 'annotation_name' => $annotation_name, + ); + return elgg_trigger_plugin_hook('permissions_check:annotate', $this->type, $params, $return); + } + + /** + * Returns the access_id. + * + * @return int The access ID + */ + public function getAccessID() { + return $this->get('access_id'); + } + + /** + * Returns the guid. + * + * @return int|null GUID + */ + public function getGUID() { + return $this->get('guid'); + } + + /** + * Returns the entity type + * + * @return string Entity type + */ + public function getType() { + return $this->get('type'); + } + + /** + * Returns the entity subtype string + * + * @note This returns a string. If you want the id, use ElggEntity::subtype. + * + * @return string The entity subtype + */ + public function getSubtype() { + // If this object hasn't been saved, then return the subtype string. + if (!((int) $this->guid > 0)) { + return $this->get('subtype'); + } + + return get_subtype_from_id($this->get('subtype')); + } + + /** + * Get the guid of the entity's owner. + * + * @return int The owner GUID + */ + public function getOwnerGUID() { + return $this->owner_guid; + } + + /** + * Return the guid of the entity's owner. + * + * @return int The owner GUID + * @deprecated 1.8 Use getOwnerGUID() + */ + public function getOwner() { + elgg_deprecated_notice("ElggEntity::getOwner deprecated for ElggEntity::getOwnerGUID", 1.8); + return $this->getOwnerGUID(); + } + + /** + * Gets the ElggEntity that owns this entity. + * + * @return ElggEntity The owning entity + */ + public function getOwnerEntity() { + return get_entity($this->owner_guid); + } + + /** + * Set the container for this object. + * + * @param int $container_guid The ID of the container. + * + * @return bool + */ + public function setContainerGUID($container_guid) { + $container_guid = (int)$container_guid; + + return $this->set('container_guid', $container_guid); + } + + /** + * Set the container for this object. + * + * @param int $container_guid The ID of the container. + * + * @return bool + * @deprecated 1.8 use setContainerGUID() + */ + public function setContainer($container_guid) { + elgg_deprecated_notice("ElggObject::setContainer deprecated for ElggEntity::setContainerGUID", 1.8); + $container_guid = (int)$container_guid; + + return $this->set('container_guid', $container_guid); + } + + /** + * Gets the container GUID for this entity. + * + * @return int + */ + public function getContainerGUID() { + return $this->get('container_guid'); + } + + /** + * Gets the container GUID for this entity. + * + * @return int + * @deprecated 1.8 Use getContainerGUID() + */ + public function getContainer() { + elgg_deprecated_notice("ElggObject::getContainer deprecated for ElggEntity::getContainerGUID", 1.8); + return $this->get('container_guid'); + } + + /** + * Get the container entity for this object. + * + * @return ElggEntity + * @since 1.8.0 + */ + public function getContainerEntity() { + return get_entity($this->getContainerGUID()); + } + + /** + * Returns the UNIX epoch time that this entity was last updated + * + * @return int UNIX epoch time + */ + public function getTimeUpdated() { + return $this->get('time_updated'); + } + + /** + * Returns the URL for this entity + * + * @return string The URL + * @see register_entity_url_handler() + * @see ElggEntity::setURL() + */ + public function getURL() { + if (!empty($this->url_override)) { + return $this->url_override; + } + return get_entity_url($this->getGUID()); + } + + /** + * Overrides the URL returned by getURL() + * + * @warning This override exists only for the life of the object. + * + * @param string $url The new item URL + * + * @return string The URL + */ + public function setURL($url) { + $this->url_override = $url; + return $url; + } + + /** + * Get the URL for this entity's icon + * + * Plugins can register for the 'entity:icon:url', <type> plugin hook + * to customize the icon for an entity. + * + * @param string $size Size of the icon: tiny, small, medium, large + * + * @return string The URL + * @since 1.8.0 + */ + public function getIconURL($size = 'medium') { + $size = elgg_strtolower($size); + + if (isset($this->icon_override[$size])) { + elgg_deprecated_notice("icon_override on an individual entity is deprecated", 1.8); + return $this->icon_override[$size]; + } + + $type = $this->getType(); + $params = array( + 'entity' => $this, + 'size' => $size, + ); + + $url = elgg_trigger_plugin_hook('entity:icon:url', $type, $params, null); + if ($url == null) { + $url = "_graphics/icons/default/$size.png"; + } + + return elgg_normalize_url($url); + } + + /** + * Returns a URL for the entity's icon. + * + * @param string $size Either 'large', 'medium', 'small' or 'tiny' + * + * @return string The url or false if no url could be worked out. + * @deprecated Use getIconURL() + */ + public function getIcon($size = 'medium') { + elgg_deprecated_notice("getIcon() deprecated by getIconURL()", 1.8); + return $this->getIconURL($size); + } + + /** + * Set an icon override for an icon and size. + * + * @warning This override exists only for the life of the object. + * + * @param string $url The url of the icon. + * @param string $size The size its for. + * + * @return bool + * @deprecated 1.8 See getIconURL() for the plugin hook to use + */ + public function setIcon($url, $size = 'medium') { + elgg_deprecated_notice("icon_override on an individual entity is deprecated", 1.8); + + $url = sanitise_string($url); + $size = sanitise_string($size); + + if (!$this->icon_override) { + $this->icon_override = array(); + } + $this->icon_override[$size] = $url; + + return true; + } + + /** + * Tests to see whether the object has been fully loaded. + * + * @return bool + */ + public function isFullyLoaded() { + return ! ($this->attributes['tables_loaded'] < $this->attributes['tables_split']); + } + + /** + * Save an entity. + * + * @return bool|int + * @throws IOException + */ + public function save() { + $guid = $this->getGUID(); + if ($guid > 0) { + + // See #5600. This ensures the lower level can_edit_entity() check will use a + // fresh entity from the DB so it sees the persisted owner_guid + _elgg_disable_caching_for_entity($guid); + + $ret = update_entity( + $guid, + $this->get('owner_guid'), + $this->get('access_id'), + $this->get('container_guid'), + $this->get('time_created') + ); + + _elgg_enable_caching_for_entity($guid); + _elgg_cache_entity($this); + + return $ret; + } else { + // Create a new entity (nb: using attribute array directly + // 'cos set function does something special!) + $this->attributes['guid'] = create_entity($this->attributes['type'], + $this->attributes['subtype'], $this->attributes['owner_guid'], + $this->attributes['access_id'], $this->attributes['site_guid'], + $this->attributes['container_guid']); + + if (!$this->attributes['guid']) { + throw new IOException(elgg_echo('IOException:BaseEntitySaveFailed')); + } + + // Save any unsaved metadata + // @todo How to capture extra information (access id etc) + if (sizeof($this->temp_metadata) > 0) { + foreach ($this->temp_metadata as $name => $value) { + $this->$name = $value; + unset($this->temp_metadata[$name]); + } + } + + // Save any unsaved annotations. + if (sizeof($this->temp_annotations) > 0) { + foreach ($this->temp_annotations as $name => $value) { + $this->annotate($name, $value); + unset($this->temp_annotations[$name]); + } + } + + // Save any unsaved private settings. + if (sizeof($this->temp_private_settings) > 0) { + foreach ($this->temp_private_settings as $name => $value) { + $this->setPrivateSetting($name, $value); + unset($this->temp_private_settings[$name]); + } + } + + // set the subtype to id now rather than a string + $this->attributes['subtype'] = get_subtype_id($this->attributes['type'], + $this->attributes['subtype']); + + _elgg_cache_entity($this); + + return $this->attributes['guid']; + } + } + + /** + * Loads attributes from the entities table into the object. + * + * @param mixed $guid GUID of entity or stdClass object from entities table + * + * @return bool + */ + protected function load($guid) { + if ($guid instanceof stdClass) { + $row = $guid; + } else { + $row = get_entity_as_row($guid); + } + + if ($row) { + // Create the array if necessary - all subclasses should test before creating + if (!is_array($this->attributes)) { + $this->attributes = array(); + } + + // Now put these into the attributes array as core values + $objarray = (array) $row; + foreach ($objarray as $key => $value) { + $this->attributes[$key] = $value; + } + + // Increment the portion counter + if (!$this->isFullyLoaded()) { + $this->attributes['tables_loaded']++; + } + + // guid needs to be an int https://github.com/elgg/elgg/issues/4111 + $this->attributes['guid'] = (int)$this->attributes['guid']; + + // Cache object handle + if ($this->attributes['guid']) { + _elgg_cache_entity($this); + } + + return true; + } + + return false; + } + + /** + * Disable this entity. + * + * Disabled entities are not returned by getter functions. + * To enable an entity, use {@link enable_entity()}. + * + * Recursively disabling an entity will disable all entities + * owned or contained by the parent entity. + * + * @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 enable_entity() + * @see ElggEntity::enable() + */ + public function disable($reason = "", $recursive = true) { + if ($r = disable_entity($this->get('guid'), $reason, $recursive)) { + $this->attributes['enabled'] = 'no'; + } + + return $r; + } + + /** + * Enable an entity + * + * @warning Disabled entities can't be loaded unless + * {@link access_show_hidden_entities(true)} has been called. + * + * @see enable_entity() + * @see access_show_hiden_entities() + * @return bool + */ + public function enable() { + if ($r = enable_entity($this->get('guid'))) { + $this->attributes['enabled'] = 'yes'; + } + + return $r; + } + + /** + * Is this entity enabled? + * + * @return boolean + */ + public function isEnabled() { + if ($this->enabled == 'yes') { + return true; + } + + return false; + } + + /** + * Delete this entity. + * + * @param bool $recursive Whether to delete all the entities contained by this entity + * + * @return bool + */ + public function delete($recursive = true) { + return delete_entity($this->get('guid'), $recursive); + } + + /* + * LOCATABLE INTERFACE + */ + + /** + * Gets the 'location' metadata for the entity + * + * @return string The location + */ + public function getLocation() { + return $this->location; + } + + /** + * Sets the 'location' metadata for the entity + * + * @todo Unimplemented + * + * @param string $location String representation of the location + * + * @return bool + */ + public function setLocation($location) { + $this->location = $location; + return true; + } + + /** + * Set latitude and longitude metadata tags for a given entity. + * + * @param float $lat Latitude + * @param float $long Longitude + * + * @return bool + * @todo Unimplemented + */ + public function setLatLong($lat, $long) { + $this->set('geo:lat', $lat); + $this->set('geo:long', $long); + + return true; + } + + /** + * Return the entity's latitude. + * + * @return float + * @todo Unimplemented + */ + public function getLatitude() { + return (float)$this->get('geo:lat'); + } + + /** + * Return the entity's longitude + * + * @return float + */ + public function getLongitude() { + return (float)$this->get('geo:long'); + } + + /* + * NOTABLE INTERFACE + */ + + /** + * Set the time and duration of an object + * + * @param int $hour If ommitted, now is assumed. + * @param int $minute If ommitted, now is assumed. + * @param int $second If ommitted, now is assumed. + * @param int $day If ommitted, now is assumed. + * @param int $month If ommitted, now is assumed. + * @param int $year If ommitted, now is assumed. + * @param int $duration Duration of event, remainder of the day is assumed. + * + * @return true + * @todo Unimplemented + */ + public function setCalendarTimeAndDuration($hour = NULL, $minute = NULL, $second = NULL, + $day = NULL, $month = NULL, $year = NULL, $duration = NULL) { + + $start = mktime($hour, $minute, $second, $month, $day, $year); + $end = $start + abs($duration); + if (!$duration) { + $end = get_day_end($day, $month, $year); + } + + $this->calendar_start = $start; + $this->calendar_end = $end; + + return true; + } + + /** + * Returns the start timestamp. + * + * @return int + * @todo Unimplemented + */ + public function getCalendarStartTime() { + return (int)$this->calendar_start; + } + + /** + * Returns the end timestamp. + * + * @todo Unimplemented + * + * @return int + */ + public function getCalendarEndTime() { + return (int)$this->calendar_end; + } + + /* + * EXPORTABLE INTERFACE + */ + + /** + * Returns an array of fields which can be exported. + * + * @return array + */ + public function getExportableValues() { + return array( + 'guid', + 'type', + 'subtype', + 'time_created', + 'time_updated', + 'container_guid', + 'owner_guid', + 'site_guid' + ); + } + + /** + * Export this class into an array of ODD Elements containing all necessary fields. + * Override if you wish to return more information than can be found in + * $this->attributes (shouldn't happen) + * + * @return array + */ + public function export() { + $tmp = array(); + + // Generate uuid + $uuid = guid_to_uuid($this->getGUID()); + + // Create entity + $odd = new ODDEntity( + $uuid, + $this->attributes['type'], + get_subtype_from_id($this->attributes['subtype']) + ); + + $tmp[] = $odd; + + $exportable_values = $this->getExportableValues(); + + // Now add its attributes + foreach ($this->attributes as $k => $v) { + $meta = NULL; + + if (in_array($k, $exportable_values)) { + switch ($k) { + case 'guid': // Dont use guid in OpenDD + case 'type': // Type and subtype already taken care of + case 'subtype': + break; + + case 'time_created': // Created = published + $odd->setAttribute('published', date("r", $v)); + break; + + case 'site_guid': // Container + $k = 'site_uuid'; + $v = guid_to_uuid($v); + $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); + break; + + case 'container_guid': // Container + $k = 'container_uuid'; + $v = guid_to_uuid($v); + $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); + break; + + case 'owner_guid': // Convert owner guid to uuid, this will be stored in metadata + $k = 'owner_uuid'; + $v = guid_to_uuid($v); + $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); + break; + + default: + $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); + } + + // set the time of any metadata created + if ($meta) { + $meta->setAttribute('published', date("r", $this->time_created)); + $tmp[] = $meta; + } + } + } + + // Now we do something a bit special. + /* + * This provides a rendered view of the entity to foreign sites. + */ + + elgg_set_viewtype('default'); + $view = elgg_view_entity($this, array('full_view' => true)); + elgg_set_viewtype(); + + $tmp[] = new ODDMetaData($uuid . "volatile/renderedentity/", $uuid, + 'renderedentity', $view, 'volatile'); + + return $tmp; + } + + /* + * IMPORTABLE INTERFACE + */ + + /** + * Import data from an parsed ODD xml data array. + * + * @param ODD $data XML data + * + * @return true + * + * @throws InvalidParameterException + */ + public function import(ODD $data) { + if (!($data instanceof ODDEntity)) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnexpectedODDClass')); + } + + // Set type and subtype + $this->attributes['type'] = $data->getAttribute('class'); + $this->attributes['subtype'] = $data->getAttribute('subclass'); + + // Set owner + $this->attributes['owner_guid'] = elgg_get_logged_in_user_guid(); // Import as belonging to importer. + + // Set time + $this->attributes['time_created'] = strtotime($data->getAttribute('published')); + $this->attributes['time_updated'] = time(); + + return true; + } + + /* + * SYSTEM LOG INTERFACE + */ + + /** + * Return an identification for the object for storage in the system log. + * This id must be an integer. + * + * @return int + */ + public function getSystemLogID() { + return $this->getGUID(); + } + + /** + * For a given ID, return the object associated with it. + * This is used by the river functionality primarily. + * + * This is useful for checking access permissions etc on objects. + * + * @param int $id GUID. + * + * @todo How is this any different or more useful than get_entity($guid) + * or new ElggEntity($guid)? + * + * @return int GUID + */ + public function getObjectFromID($id) { + return get_entity($id); + } + + /** + * Returns tags for this entity. + * + * @warning Tags must be registered by {@link elgg_register_tag_metadata_name()}. + * + * @param array $tag_names Optionally restrict by tag metadata names. + * + * @return array + */ + public function getTags($tag_names = NULL) { + if ($tag_names && !is_array($tag_names)) { + $tag_names = array($tag_names); + } + + $valid_tags = elgg_get_registered_tag_metadata_names(); + $entity_tags = array(); + + foreach ($valid_tags as $tag_name) { + if (is_array($tag_names) && !in_array($tag_name, $tag_names)) { + continue; + } + + if ($tags = $this->$tag_name) { + // if a single tag, metadata returns a string. + // if multiple tags, metadata returns an array. + if (is_array($tags)) { + $entity_tags = array_merge($entity_tags, $tags); + } else { + $entity_tags[] = $tags; + } + } + } + + return $entity_tags; + } +} |