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; +	} +} | 
