* @copyright Curverider Ltd 2008 * @link http://elgg.org/ */ /// Cache objects in order to minimise database access. $ENTITY_CACHE = array(); /// Cache subtype searches $SUBTYPE_CACHE = array(); /** * ElggEntity The elgg entity superclass * This class holds methods for accessing the main entities table. * * @author Curverider Ltd * @package Elgg * @subpackage Core */ abstract class ElggEntity implements Exportable, // Allow export of data Importable, // Allow import of data Loggable, // Can events related to this object class be logged Iterator, // Override foreach behaviour ArrayAccess // Override for array access { /** * The main attributes of an entity. * Blank entries for all database fields should be created by the constructor. * Subclasses should add to this in their constructors. * Any field not appearing in this will be viewed as a */ protected $attributes; /** * If set, overrides the value of getURL() */ protected $url_override; /** * Icon override, overrides the value of getIcon(). */ protected $icon_override; /** * Temporary cache for metadata, permitting meta data access before a guid has obtained. */ protected $temp_metadata; /** * Temporary cache for annotations, permitting meta data access before a guid has obtained. */ protected $temp_annotations; /** * Initialise the attributes array. * This is vital to distinguish between metadata and base parameters. * * Place your base parameters here. * * @return void */ protected function initialise_attributes() { initialise_entity_cache(); // Create attributes array if not already created if (!is_array($this->attributes)) $this->attributes = array(); if (!is_array($this->temp_metadata)) $this->temp_metadata = array(); if (!is_array($this->temp_annotations)) $this->temp_annotations = array(); $this->attributes['guid'] = ""; $this->attributes['type'] = ""; $this->attributes['subtype'] = ""; $this->attributes['owner_guid'] = $_SESSION['guid']; $this->attributes['container_guid'] = $_SESSION['guid']; $this->attributes['site_guid'] = 0; $this->attributes['access_id'] = 0; $this->attributes['time_created'] = ""; $this->attributes['time_updated'] = ""; // 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; } /** * Return the value of a given key. * If $name is a key field (as defined in $this->attributes) that value is returned, otherwise it will * then look to see if the value is in this object'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. * * @param string $name * @return mixed Returns the value of a given value, or null. */ public function get($name) { // See if its in our base attribute if (isset($this->attributes[$name])) { return $this->attributes[$name]; } // No, so see if its in the meta data for this entity $meta = $this->getMetaData($name); if ($meta) return $meta; // Can't find it, so return null return null; } /** * Set the value of a given key, replacing it if necessary. * If $name is a base attribute (as defined in $this->attributes) that value is set, otherwise it will * set the appropriate item of metadata. * * Note: 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. * * @param string $name * @param mixed $value */ public function set($name, $value) { if (array_key_exists($name, $this->attributes)) { // Check that we're not trying to change the guid! if ((array_key_exists('guid', $this->attributes)) && ($name=='guid')) return false; $this->attributes[$name] = $value; } else return $this->setMetaData($name, $value); return true; } /** * Get a given piece of metadata. * * @param string $name */ public function getMetaData($name) { if ((int) ($this->guid) > 0) { $md = get_metadata_byname($this->getGUID(), $name); } else { if (isset($this->temp_metadata[$name])) return $this->temp_metadata[$name]; } if ($md && !is_array($md)) { return $md->value; } else if ($md && is_array($md)) { return metadata_array_to_values($md); } return null; } /** * Class member get overloading * * @param string $name * @return mixed */ function __get($name) { return $this->get($name); } /** * Class member set overloading * * @param string $name * @param mixed $value * @return mixed */ function __set($name, $value) { return $this->set($name, $value); } /** * Supporting isset. * * @param string $name The name of the attribute or metadata. * @return bool */ function __isset($name) { if ($this->$name!="") return true; else return false; } /** * Supporting unsetting of magic attributes. * * @param string $name The name of the attribute or metadata. */ function __unset($name) { if (array_key_exists($name, $this->attributes)) $this->attributes[$name] = ""; else $this->clearMetaData($name); } /** * Set a piece of metadata. * * @param string $name * @param mixed $value * @param string $value_type * @param bool $multiple * @return bool */ public function setMetaData($name, $value, $value_type = "", $multiple = false) { if (is_array($value)) { unset($this->temp_metadata[$name]); remove_metadata($this->getGUID(), $name); foreach ($value as $v) { if ((int) $this->guid > 0) { $multiple = true; if (!create_metadata($this->getGUID(), $name, $v, $value_type, $this->getOwner(), $this->getAccessID(), $multiple)) return false; } else { if (($multiple) && (isset($this->temp_metadata[$name]))) { if (!is_array($this->temp_metadata[$name])) { $tmp = $this->temp_metadata[$name]; $this->temp_metadata[$name] = array(); $this->temp_metadata[$name][] = $tmp; } $this->temp_metadata[$name][] = $value; } else $this->temp_metadata[$name] = $value; } } return true; } else { unset($this->temp_metadata[$name]); if ((int) $this->guid > 0) { return create_metadata($this->getGUID(), $name, $value, $value_type, $this->getOwner(), $this->getAccessID(), $multiple); } else { //$this->temp_metadata[$name] = $value; if (($multiple) && (isset($this->temp_metadata[$name]))) { if (!is_array($this->temp_metadata[$name])) { $tmp = $this->temp_metadata[$name]; $this->temp_metadata[$name] = array(); $this->temp_metadata[$name][] = $tmp; } $this->temp_metadata[$name][] = $value; } else $this->temp_metadata[$name] = $value; return true; } } } /** * Clear metadata. */ public function clearMetaData($name = "") { if (empty($name)) { return clear_metadata($this->getGUID()); } else { return remove_metadata($this->getGUID(),$name); } } /** * Remove all entities associated with this entity * * @return true */ public function clearRelationships() { remove_entity_relationships($this->getGUID()); remove_entity_relationships($this->getGUID(),"",true); return true; } /** * Adds an annotation to an entity. By default, the type is detected automatically; however, * it can also be set. Note that by default, annotations are private. * * @param string $name * @param mixed $value * @param int $access_id * @param int $owner_id * @param string $vartype */ function annotate($name, $value, $access_id = 0, $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; } /** * Get the annotations for an entity. * * @param string $name * @param int $limit * @param int $offset * @param string $order */ function getAnnotations($name, $limit = 50, $offset = 0, $order="asc") { if ((int) ($this->guid) > 0) { return get_annotations($this->getGUID(), "", "", $name, "", 0, $limit, $offset, $order); } else { return $this->temp_annotations[$name]; } } /** * Remove all annotations or all annotations for this entity. * * @param string $name */ function clearAnnotations($name = "") { return clear_annotations($this->getGUID(), $name); } /** * Return the annotations for the entity. * * @param string $name The type of annotation. */ function countAnnotations($name) { return count_annotations($this->getGUID(), "","",$name); } /** * Get the average of an integer type annotation. * * @param string $name */ function getAnnotationsAvg($name) { return get_annotations_avg($this->getGUID(), "","",$name); } /** * Get the sum of integer type annotations of a given name. * * @param string $name */ function getAnnotationsSum($name) { return get_annotations_sum($this->getGUID(), "","",$name); } /** * Get the minimum of integer type annotations of given name. * * @param string $name */ function getAnnotationsMin($name) { return get_annotations_min($this->getGUID(), "","",$name); } /** * Get the maximum of integer type annotations of a given name. * * @param string $name */ function getAnnotationsMax($name) { return get_annotations_max($this->getGUID(), "","",$name); } /** * Gets an array of entities from a specific relationship type * * @param string $relationship Relationship type (eg "friends") * @param true|false $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 get_entities_from_relationship($relationship,$this->getGUID(),$inverse,"","","","time_created desc",$limit,$offset); } /** * Gets the number of of entities from a specific relationship type * * @param string $relationship Relationship type (eg "friends") * @return int|false The number of entities or false on failure */ function countEntitiesFromRelationship($relationship) { return get_entities_from_relationship($relationship,$this->getGUID(),false,"","","","time_created desc",null,null,true); } /** * Determines whether or not the specified user (by default the current one) can edit the entity * * @param int $user_guid The user GUID, optionally (defaults to the currently logged in user) * @return true|false */ function canEdit($user_guid = 0) { return can_edit_entity($this->getGUID(),$user_guid); } /** * Determines whether or not the specified user (by default the current one) can edit metadata on the entity * * @param ElggMetadata $metadata The piece of metadata to specifically check * @param int $user_guid The user GUID, optionally (defaults to the currently logged in user) * @return true|false */ function canEditMetadata($metadata = null, $user_guid = 0) { return can_edit_entity_metadata($this->getGUID(), $user_guid, $metadata); } /** * Returns whether the given user (or current user) has the ability to write to this group. * * @param int $user_guid The user. * @return bool */ public function canWriteToContainer($user_guid = 0) { return can_write_to_container($user_guid, $this->getGUID()); } /** * Obtain this entity's access ID * * @return int The access ID */ public function getAccessID() { return $this->get('access_id'); } /** * Obtain this entity's GUID * * @return int GUID */ public function getGUID() { return $this->get('guid'); } /** * Get the owner of this entity * * @return int The owner GUID */ public function getOwner() { return $this->get('owner_guid'); } /** * Returns the actual entity of the user who owns this entity, if any * * @return ElggEntity The owning user */ public function getOwnerEntity() { return get_entity($this->get('owner_guid')); } /** * Gets the type of entity this is * * @return string Entity type */ public function getType() { return $this->get('type'); } /** * Returns the subtype of this entity * * @return string The entity subtype */ public function getSubtype() { if (!((int) $this->guid > 0)) return $this->get('subtype'); // If this object hasn't been saved, then return the subtype string. return get_subtype_from_id($this->get('subtype')); } /** * Gets the UNIX epoch time that this entity was created * * @return int UNIX epoch time */ public function getTimeCreated() { return $this->get('time_created'); } /** * Gets the UNIX epoch time that this entity was last updated * * @return int UNIX epoch time */ public function getTimeUpdated() { return $this->get('time_updated'); } /** * Gets the display URL for this entity * * @return string The URL */ public function getURL() { if (!empty($this->url_override)) return $this->url_override; return get_entity_url($this->getGUID()); } /** * Overrides the URL returned by getURL * * @param string $url The new item URL * @return string The URL */ public function setURL($url) { $this->url_override = $url; return $url; } /** * Return a url for the entity's icon, trying multiple alternatives. * * @param string $size Either 'large','medium','small' or 'tiny' * @return string The url or false if no url could be worked out. */ public function getIcon($size = 'medium') { if (isset($this->icon_override[$size])) return $this->icon_override[$size]; return get_entity_icon_url($this, $size); } /** * Set an icon override for an icon and size. * * @param string $url The url of the icon. * @param string $size The size its for. * @return bool */ public function setIcon($url, $size = 'medium') { $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 generic attributes to the entities table. */ public function save() { $guid = (int) $this->guid; if ($guid > 0) { cache_entity($this); return update_entity( $this->get('guid'), $this->get('owner_guid'), $this->get('access_id') ); } else { $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']); // Create a new entity (nb: using attribute array directly 'cos set function does something special!) 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 metadata. TODO: How to capture extra information (access id etc) if (sizeof($this->temp_annotations) > 0) { foreach($this->temp_annotations as $name => $value) { $this->annotate($name, $value); unset($this->temp_annotations[$name]); } } // Cache object handle if ($this->attributes['guid']) cache_entity($this); return $this->attributes['guid']; } } /** * Load the basic entity information and populate base attributes array. * * @param int $guid */ protected function load($guid) { $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'] ++; // Cache object handle if ($this->attributes['guid']) cache_entity($this); return true; } return false; } /** * Disable this entity. */ public function disable() { return disable_entity($this->get('guid')); } /** * Re-enable this entity. */ public function enable() { return enable_entity($this->get('guid')); } /** * Delete this entity. */ public function delete() { $res = delete_entity($this->get('guid')); return $res; } // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// /** * Export this class into a stdClass containing all necessary fields. * Override if you wish to return more information than can be found in $this->attributes (shouldn't happen) * * @return stdClass */ 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; // Now add its attributes foreach ($this->attributes as $k => $v) { $meta = NULL; switch ($k) { case 'guid' : // Dont use guid case 'subtype' : // The subtype case 'type' : // Don't use type case 'access_id' : // Don't use access - if can export then its public for you, then importer decides what access to give this object. case 'time_updated' : // Don't use date in export case 'tables_split' : // We don't want to export the internal counter variables. case 'tables_loaded' : // Or this one case 'enabled' : // Useless to an importer case 'email' : case 'password' : // Definitely don't want these two 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, true); elgg_set_viewtype(); $tmp[] = new ODDMetaData($uuid . "volatile/renderedentity/", $uuid, 'renderedentity', $view , 'volatile'); return $tmp; } // IMPORTABLE INTERFACE //////////////////////////////////////////////////////////// /** * Import data from an parsed xml data array. * * @param array $data * @param int $version */ 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'] = $_SESSION['id']; // 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(); } /** * Return the class name of the object. */ public function getClassName() { return get_class($this); } /** * 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. */ public function getObjectFromID($id) { return get_entity($id); } /** * Return the GUID of the owner of this object. */ public function getObjectOwnerGUID() { return $this->owner_guid; } // ITERATOR INTERFACE ////////////////////////////////////////////////////////////// /* * This lets an entity's attributes be displayed using foreach as a normal array. * Example: http://www.sitepoint.com/print/php5-standard-library */ private $valid = FALSE; function rewind() { $this->valid = (FALSE !== reset($this->attributes)); } function current() { return current($this->attributes); } function key() { return key($this->attributes); } function next() { $this->valid = (FALSE !== next($this->attributes)); } function valid() { return $this->valid; } // ARRAY ACCESS INTERFACE ////////////////////////////////////////////////////////// /* * This lets an entity's attributes be accessed like an associative array. * Example: http://www.sitepoint.com/print/php5-standard-library */ function offsetSet($key, $value) { if ( array_key_exists($key, $this->attributes) ) { $this->attributes[$key] = $value; } } function offsetGet($key) { if ( array_key_exists($key, $this->attributes) ) { return $this->attributes[$key]; } } function offsetUnset($key) { if ( array_key_exists($key, $this->attributes) ) { $this->attributes[$key] = ""; // Full unsetting is dangerious for our objects } } function offsetExists($offset) { return array_key_exists($offset, $this->attributes); } } /** * Initialise the entity cache. */ function initialise_entity_cache() { global $ENTITY_CACHE; if (!is_array($ENTITY_CACHE)) $ENTITY_CACHE = array(); } /** * Invalidate this class' entry in the cache. * * @param int $guid The guid */ function invalidate_cache_for_entity($guid) { global $ENTITY_CACHE; $guid = (int)$guid; unset($ENTITY_CACHE[$guid]); } /** * Cache an entity. * * @param ElggEntity $entity Entity to cache */ function cache_entity(ElggEntity $entity) { global $ENTITY_CACHE; $ENTITY_CACHE[$entity->guid] = $entity; } /** * Retrieve a entity from the cache. * * @param int $guid The guid */ function retrieve_cached_entity($guid) { global $ENTITY_CACHE; $guid = (int)$guid; if (isset($ENTITY_CACHE[$guid])) if ($ENTITY_CACHE[$guid]->isFullyLoaded()) return $ENTITY_CACHE[$guid]; return false; } /** * As retrieve_cached_entity, but returns the result as a stdClass (compatible with load functions that * expect a database row.) * * @param int $guid The guid */ function retrieve_cached_entity_row($guid) { $obj = retrieve_cached_entity($guid); if ($obj) { $tmp = new stdClass; foreach ($obj as $k => $v) $tmp->$k = $v; return $tmp; } return false; } /** * Return the integer ID for a given subtype, or false. * * TODO: Move to a nicer place? * * @param string $type * @param string $subtype */ function get_subtype_id($type, $subtype) { global $CONFIG, $SUBTYPE_CACHE; $type = sanitise_string($type); $subtype = sanitise_string($subtype); if ($subtype=="") return $subtype; // Todo: cache here? Or is looping less efficient that going to the db each time? $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where type='$type' and subtype='$subtype'"); if ($result) { $SUBTYPE_CACHE[$result->id] = $result; return $result->id; } return 0; } /** * For a given subtype ID, return its identifier text. * * TODO: Move to a nicer place? * * @param int $subtype_id */ function get_subtype_from_id($subtype_id) { global $CONFIG, $SUBTYPE_CACHE; $subtype_id = (int)$subtype_id; if (!$subtype_id) return false; if (isset($SUBTYPE_CACHE[$subtype_id])) return $SUBTYPE_CACHE[$subtype_id]->subtype; $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where id=$subtype_id"); if ($result) { $SUBTYPE_CACHE[$subtype_id] = $result; return $result->subtype; } return false; } /** * This function tests to see if a subtype has a registered class handler. * * @param string $type The type * @param string $subtype The subtype * @return a class name or null */ function get_subtype_class($type, $subtype) { global $CONFIG, $SUBTYPE_CACHE; $type = sanitise_string($type); $subtype = sanitise_string($subtype); // Todo: cache here? Or is looping less efficient that going to the db each time? $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where type='$type' and subtype='$subtype'"); if ($result) { $SUBTYPE_CACHE[$result->id] = $result; return $result->class; } return NULL; } /** * This function tests to see if a subtype has a registered class handler by its id. * * @param int $subtype_id The subtype * @return a class name or null */ function get_subtype_class_from_id($subtype_id) { global $CONFIG, $SUBTYPE_CACHE; $subtype_id = (int)$subtype_id; if (!$subtype_id) return false; if (isset($SUBTYPE_CACHE[$subtype_id])) return $SUBTYPE_CACHE[$subtype_id]->class; $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where id=$subtype_id"); if ($result) { $SUBTYPE_CACHE[$subtype_id] = $result; return $result->class; } return NULL; } /** * This function will register a new subtype, returning its ID as required. * * @param string $type The type you're subtyping * @param string $subtype The subtype label * @param string $class Optional class handler (if you don't want it handled by the generic elgg handler for the type) */ function add_subtype($type, $subtype, $class = "") { global $CONFIG; $type = sanitise_string($type); $subtype = sanitise_string($subtype); $class = sanitise_string($class); // Short circuit if no subtype is given if ($subtype == "") return 0; $id = get_subtype_id($type, $subtype); if ($id==0) return insert_data("insert into {$CONFIG->dbprefix}entity_subtypes (type, subtype, class) values ('$type','$subtype','$class')"); return $id; } /** * Update an existing entity. * * @param int $guid * @param int $owner_guid * @param int $access_id */ function update_entity($guid, $owner_guid, $access_id) { global $CONFIG, $ENTITY_CACHE; $guid = (int)$guid; $owner_guid = (int)$owner_guid; $access_id = (int)$access_id; $time = time(); $entity = get_entity($guid); if ($entity->canEdit()) { if (trigger_elgg_event('update',$entity->type,$entity)) { $ret = update_data("UPDATE {$CONFIG->dbprefix}entities set owner_guid='$owner_guid', access_id='$access_id', time_updated='$time' WHERE guid=$guid"); // Handle cases where there was no error BUT no rows were updated! if ($ret===false) return false; return true; } } } /** * Determine whether a given user is able to write to a given container. * * @param int $user_guid The user guid, or 0 for $_SESSION['user']->getGUID() * @param int $container_guid The container, or 0 for the current page owner. */ function can_write_to_container($user_guid = 0, $container_guid = 0, $entity_type = 'all') { global $CONFIG; $user_guid = (int)$user_guid; if (!$user_guid) $user_guid = (int) $_SESSION['guid']; $user = get_entity($user_guid); $container_guid = (int)$container_guid; if (!$container_guid) $container_guid = page_owner(); if (!$container_guid) return true; $container = get_entity($container_guid); if (($container) && ($user)) { // If the user can edit the container, they can also write to it if ($container->canEdit()) return true; // Basics, see if the user is a member of the group. if ($container instanceof ElggGroup) { if (!$container->isMember($user)) { return false; } else { return true; } } // See if anyone else has anything to say return trigger_plugin_hook('container_permissions_check',$entity_type,array('container' => $container, 'user' => $user), false); } return false; } /** * Create a new entity of a given type. * * @param string $type The type of the entity (site, user, object). * @param string $subtype The subtype of the entity. * @param int $owner_guid The GUID of the object's owner. * @param int $access_id The access control group to create the entity with. * @param int $site_guid The site to add this entity to. Leave as 0 (default) for the current site. * @return mixed The new entity's GUID, or false on failure */ function create_entity($type, $subtype, $owner_guid, $access_id, $site_guid = 0, $container_guid = 0) { global $CONFIG; $type = sanitise_string($type); $subtype = add_subtype($type, $subtype); $owner_guid = (int)$owner_guid; $access_id = (int)$access_id; $time = time(); if ($site_guid == 0) $site_guid = $CONFIG->site_guid; $site_guid = (int) $site_guid; if ($container_guid == 0) $container_guid = $owner_guid; if (!can_write_to_container($owner_guid, $container_guid, $type)) return false; if ($type=="") throw new InvalidParameterException(elgg_echo('InvalidParameterException:EntityTypeNotSet')); return insert_data("INSERT into {$CONFIG->dbprefix}entities (type, subtype, owner_guid, site_guid, container_guid, access_id, time_created, time_updated) values ('$type',$subtype, $owner_guid, $site_guid, $container_guid, $access_id, $time, $time)"); } /** * Retrieve the entity details for a specific GUID, returning it as a stdClass db row. * * You will only get an object if a) it exists, b) you have access to it. * * @param int $guid The GUID of the object to extract */ function get_entity_as_row($guid) { global $CONFIG; $guid = (int) $guid; $row = retrieve_cached_entity_row($guid); if ($row) { // We have already cached this object, so retrieve its value from the cache if (isset($CONFIG->debug) && $CONFIG->debug) error_log("** Retrieving GUID:$guid from cache"); return $row; } else { // Object not cached, load it. if (isset($CONFIG->debug) && $CONFIG->debug == true) error_log("** GUID:$guid loaded from DB"); $access = get_access_sql_suffix(); return get_data_row("SELECT * from {$CONFIG->dbprefix}entities where guid=$guid and $access"); } } /** * Create an Elgg* object from a given entity row. */ function entity_row_to_elggstar($row) { if (!($row instanceof stdClass)) return $row; // See if there are any registered subtype handler classes for this type and subtype $classname = get_subtype_class_from_id($row->subtype); if ($classname!="") { if (class_exists($classname)) { $tmp = new $classname($row); if (!($tmp instanceof ElggEntity)) throw new ClassException(sprintf(elgg_echo('ClassException:ClassnameNotClass'), $classname, get_class())); return $tmp; } else error_log(sprintf(elgg_echo('ClassNotFoundException:MissingClass'), $classname)); } else { switch ($row->type) { case 'object' : return new ElggObject($row); case 'user' : return new ElggUser($row); case 'group' : return new ElggGroup($row); case 'site' : return new ElggSite($row); default: throw new InstallationException(sprintf(elgg_echo('InstallationException:TypeNotSupported'), $row->type)); } } return false; } /** * Return the entity for a given guid as the correct object. * @param int $guid The GUID of the entity * @return a child of ElggEntity appropriate for the type. */ function get_entity($guid) { return entity_row_to_elggstar(get_entity_as_row($guid)); } /** * Return entities matching a given query, or the number thereof * * @param string $type The type of entity (eg "user", "object" etc) * @param string $subtype The arbitrary subtype of the entity * @param int $owner_guid The GUID of the owning user * @param string $order_by The field to order by; by default, time_created desc * @param int $limit The number of entities to return; 10 by default * @param int $offset The indexing offset, 0 by default * @param boolean $count Set to true to get a count rather than the entities themselves (limits and offsets don't apply in this context). Defaults to false. * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. * @param int|array $container_guid The container or containers to get entities from (default: all containers). * @return array A list of entities. */ function get_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = null) { global $CONFIG; if ($subtype === false || $subtype === null || $subtype === 0) return false; if ($order_by == "") $order_by = "time_created desc"; $order_by = sanitise_string($order_by); $limit = (int)$limit; $offset = (int)$offset; $site_guid = (int) $site_guid; if ($site_guid == 0) $site_guid = $CONFIG->site_guid; $where = array(); if (is_array($type)) { $tempwhere = ""; if (sizeof($type)) foreach($type as $typekey => $subtypearray) { foreach($subtypearray as $subtypeval) { $typekey = sanitise_string($typekey); if (!empty($subtypeval)) { $subtypeval = (int) get_subtype_id($typekey, $subtypeval); } else { $subtypeval = 0; } if (!empty($tempwhere)) $tempwhere .= " or "; $tempwhere .= "(type = '{$typekey}' and subtype = {$subtypeval})"; } } if (!empty($tempwhere)) $where[] = "({$tempwhere})"; } else { $type = sanitise_string($type); $subtype = get_subtype_id($type, $subtype); if ($type != "") $where[] = "type='$type'"; if ($subtype!=="") $where[] = "subtype=$subtype"; } if ($owner_guid != "") { if (!is_array($owner_guid)) { $owner_array = array($owner_guid); $owner_guid = (int) $owner_guid; // $where[] = "owner_guid = '$owner_guid'"; } else if (sizeof($owner_guid) > 0) { $owner_array = array_map('sanitise_int', $owner_guid); // Cast every element to the owner_guid array to int // $owner_guid = array_map("sanitise_int", $owner_guid); // $owner_guid = implode(",",$owner_guid); // $where[] = "owner_guid in ({$owner_guid})"; } if (is_null($container_guid)) { $container_guid = $owner_array; } } if ($site_guid > 0) $where[] = "site_guid = {$site_guid}"; if (!is_null($container_guid)) { if (is_array($container_guid)) { foreach($container_guid as $key => $val) $container_guid[$key] = (int) $val; $where[] = "container_guid in (" . implode(",",$container_guid) . ")"; } else { $container_guid = (int) $container_guid; $where[] = "container_guid = {$container_guid}"; } } if (!$count) { $query = "SELECT * from {$CONFIG->dbprefix}entities where "; } else { $query = "select count(guid) as total from {$CONFIG->dbprefix}entities where "; } foreach ($where as $w) $query .= " $w and "; $query .= get_access_sql_suffix(); // Add access controls if (!$count) { $query .= " order by $order_by"; if ($limit) $query .= " limit $offset, $limit"; // Add order and limit $dt = get_data($query, "entity_row_to_elggstar"); return $dt; } else { $total = get_data_row($query); return $total->total; } } /** * Returns a viewable list of entities * * @see elgg_view_entity_list * * @param string $type The type of entity (eg "user", "object" etc) * @param string $subtype The arbitrary subtype of the entity * @param int $owner_guid The GUID of the owning user * @param int $limit The number of entities to display per page (default: 10) * @param true|false $fullview Whether or not to display the full view (default: true) * @param true|false $viewtypetoggle Whether or not to allow gallery view * @return string A viewable list of entities */ function list_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false) { $offset = (int) get_input('offset'); $count = get_entities($type, $subtype, $owner_guid, "", $limit, $offset, true); $entities = get_entities($type, $subtype, $owner_guid, "", $limit, $offset); return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle); } /** * Returns a viewable list of entities contained in a number of groups. * * @param string $subtype The arbitrary subtype of the entity * @param int $owner_guid The GUID of the owning user * @param int $container_guid The GUID of the containing group * @param int $limit The number of entities to display per page (default: 10) * @param true|false $fullview Whether or not to display the full view (default: true) * @return string A viewable list of entities */ function list_entities_groups($subtype = "", $owner_guid = 0, $container_guid = 0, $limit = 10, $fullview = true) { $offset = (int) get_input('offset'); $count = get_objects_in_group($container_guid, $subtype, $owner_guid, 0, "", $limit, $offset, true); $entities = get_objects_in_group($container_guid, $subtype, $owner_guid, 0, "", $limit, $offset); return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview); } /** * Disable an entity but not delete it. * * @param int $guid */ function disable_entity($guid) { global $CONFIG; $guid = (int)$guid; if ($entity = get_entity($guid)) { if (trigger_elgg_event('delete',$entity->type,$entity)) { if ($entity->canEdit()) { $res = update_data("UPDATE {$CONFIG->dbprefix}entities set enabled='no' where guid={$guid}"); return $res; } } } return false; } /** * Enable an entity again. * * @param int $guid */ function enable_entity($guid) { global $CONFIG; $guid = (int)$guid; return update_data("UPDATE {$CONFIG->dbprefix}entities set enabled='yes' where guid={$guid}"); } /** * Delete a given entity. * * @param int $guid */ function delete_entity($guid) { global $CONFIG; // TODO Make sure this deletes all metadata/annotations/relationships/etc!! $guid = (int)$guid; if ($entity = get_entity($guid)) { if (trigger_elgg_event('delete',$entity->type,$entity)) { if ($entity->canEdit()) { $entity->clearMetadata(); $entity->clearAnnotations(); $entity->clearRelationships(); $res = delete_data("DELETE from {$CONFIG->dbprefix}entities where guid={$guid}"); return $res; } } } return false; } /** * Delete multiple entities that match a given query. * This function itterates through and calls delete_entity on each one, this is somewhat inefficient but lets * the 'delete' even be called for each entity. * * @param string $type The type of entity (eg "user", "object" etc) * @param string $subtype The arbitrary subtype of the entity * @param int $owner_guid The GUID of the owning user */ function delete_entities($type = "", $subtype = "", $owner_guid = 0) { $entities = get_entities($type, $subtype, $owner_guid, "time_created desc", 0); foreach ($entities as $entity) delete_entity($entity->guid); return true; } /** * A plugin hook to get certain volitile (generated on the fly) attributes about an entity in order to export them. * * @param unknown_type $hook * @param unknown_type $entity_type * @param unknown_type $returnvalue * @param unknown_type $params The parameters, passed 'guid' and 'varname' * @return unknown */ function volatile_data_export_plugin_hook($hook, $entity_type, $returnvalue, $params) { $guid = (int)$params['guid']; $variable_name = sanitise_string($params['varname']); if (($hook == 'volatile') && ($entity_type == 'metadata')) { if (($guid) && ($variable_name)) { switch ($variable_name) { case 'renderedentity' : elgg_set_viewtype('default'); $view = elgg_view_entity(get_entity($guid)); elgg_set_viewtype(); $tmp = new ElggMetadata(); $tmp->type = 'volatile'; $tmp->name = 'renderedentity'; $tmp->value = $view; $tmp->entity_guid = $guid; return $tmp; break; } } } } /** * Handler called by trigger_plugin_hook on the "export" event. */ function export_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { // Sanity check values if ((!is_array($params)) && (!isset($params['guid']))) throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); if (!is_array($returnvalue)) throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonArrayReturnValue')); $guid = (int)$params['guid']; // Get the entity $entity = get_entity($guid); if (!($entity instanceof ElggEntity)) throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class())); $export = $entity->export(); if (is_array($export)) foreach ($export as $e) $returnvalue[] = $e; else $returnvalue[] = $export; return $returnvalue; } /** * Utility function used by import_entity_plugin_hook() to process an ODDEntity into an unsaved ElggEntity. * * @param ODDEntity $element The OpenDD element * @return ElggEntity the unsaved entity which should be populated by items. */ function oddentity_to_elggentity(ODDEntity $element) { $class = $element->getAttribute('class'); $subclass = $element->getAttribute('subclass'); // See if we already have imported this uuid $tmp = get_entity_from_uuid($element->getAttribute('uuid')); if (!$tmp) { // Construct new class with owner from session $classname = get_subtype_class($class, $subclass); if ($classname!="") { if (class_exists($classname)) { $tmp = new $classname(); if (!($tmp instanceof ElggEntity)) throw new ClassException(sprintf(elgg_echo('ClassException:ClassnameNotClass', $classname, get_class()))); } else error_log(sprintf(elgg_echo('ClassNotFoundException:MissingClass'), $classname)); } else { switch ($class) { case 'object' : $tmp = new ElggObject($row); break; case 'user' : $tmp = new ElggUser($row); break; case 'group' : $tmp = new ElggGroup($row); break; case 'site' : $tmp = new ElggSite($row); break; default: throw new InstallationException(sprintf(elgg_echo('InstallationException:TypeNotSupported'), $class)); } } } if ($tmp) { if (!$tmp->import($element)) throw new ImportException(sprintf(elgg_echo('ImportException:ImportFailed'), $element->getAttribute('uuid'))); return $tmp; } return NULL; } /** * Import an entity. * This function checks the passed XML doc (as array) to see if it is a user, if so it constructs a new * elgg user and returns "true" to inform the importer that it's been handled. */ function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { $element = $params['element']; $tmp = NULL; if ($element instanceof ODDEntity) { $tmp = oddentity_to_elggentity($element); if ($tmp) { if (!$tmp->save()) // Make sure its saved throw new ImportException(sprintf(elgg_echo('ImportException:ProblemSaving'), $element->getAttribute('uuid'))); // Belts and braces if (!$tmp->guid) throw new ImportException(elgg_echo('ImportException:NoGUID')); add_uuid_to_guid($tmp->guid, $element->getAttribute('uuid')); // We have saved, so now tag return $tmp; } } } /** * Determines whether or not the specified user can edit the specified entity. * * This is extendible by registering a plugin hook taking in the parameters 'entity' and 'user', * which are the entity and user entities respectively * * @see register_plugin_hook * * @param int $entity_guid The GUID of the entity * @param int $user_guid The GUID of the user * @return true|false Whether the specified user can edit the specified entity. */ function can_edit_entity($entity_guid, $user_guid = 0) { global $CONFIG; if ($user_guid == 0) { if (isset($_SESSION['user'])) { $user = $_SESSION['user']; } else { $user = null; } } else { $user = get_entity($user_guid); } if ($entity = get_entity($entity_guid)) { // Test user if possible - should default to false unless a plugin hook says otherwise if (!is_null($user)) { if ($entity->getOwner() == $user->getGUID()) return true; if ($entity->container_guid == $user->getGUID()) return true; if ($entity->type == "user" && $entity->getGUID() == $user->getGUID()) return true; if ($container_entity = get_entity($entity->container_guid)) { if ($container_entity->canEdit()) return true; } } return trigger_plugin_hook('permissions_check',$entity->type,array('entity' => $entity, 'user' => $user),false); } else { return false; } } /** * Determines whether or not the specified user can edit metadata on the specified entity. * * This is extendible by registering a plugin hook taking in the parameters 'entity' and 'user', * which are the entity and user entities respectively * * @see register_plugin_hook * * @param int $entity_guid The GUID of the entity * @param int $user_guid The GUID of the user * @param ElggMetadata $metadata The metadata to specifically check (if any; default null) * @return true|false Whether the specified user can edit the specified entity. */ function can_edit_entity_metadata($entity_guid, $user_guid = 0, $metadata = null) { if ($entity = get_entity($entity_guid)) { $return = null; if ($metadata->owner_guid == 0) $return = true; if (is_null($return)) $return = can_edit_entity($entity_guid, $user_guid); $user = get_entity($user_guid); $return = trigger_plugin_hook('permissions_check:metadata',$entity->type,array('entity' => $entity, 'user' => $user, 'metadata' => $metadata),$return); return $return; } else { return false; } } /** * Get the icon for an entity * * @param ElggEntity $entity The entity (passed an entity rather than a guid to handle non-created entities) * @param string $size */ function get_entity_icon_url(ElggEntity $entity, $size = 'medium') { global $CONFIG; $size = sanitise_string($size); switch (strtolower($size)) { case 'master': case 'large' : $size = 'large'; break; case 'topbar' : case 'tiny' : $size = 'tiny'; break; case 'small' : $size = 'small'; break; case 'medium' : default: $size = 'medium'; } $url = false; $viewtype = elgg_get_viewtype(); // Step one, see if anyone knows how to render this in the current view $url = trigger_plugin_hook('entity:icon:url', $type, array('entity' => $entity, 'viewtype' => $viewtype, 'size' => $size), $url); if(!@getimagesize($url)) $url = false; // If not exist then don't use the url // Fail, so use default if (!$url) $url = $CONFIG->url . "_graphics/icons/default/$size.png"; return $url; } /** * Gets the URL for an entity, given a particular GUID * * @param int $entity_guid The GUID of the entity * @return string The URL of the entity */ function get_entity_url($entity_guid) { global $CONFIG; if ($entity = get_entity($entity_guid)) { $url = ""; if (isset($CONFIG->entity_url_handler[$entity->getType()][$entity->getSubType()])) { $function = $CONFIG->entity_url_handler[$entity->getType()][$entity->getSubType()]; if (is_callable($function)) { $url = $function($entity); } } if (isset($CONFIG->entity_url_handler[$entity->getType()]['all'])) { $function = $CONFIG->entity_url_handler[$entity->getType()]['all']; if (is_callable($function)) { $url = $function($entity); } } if (isset($CONFIG->entity_url_handler['all']['all'])) { $function = $CONFIG->entity_url_handler['all']['all']; if (is_callable($function)) { $url = $function($entity); } } if ($url == "") { $url = $CONFIG->url . "pg/view/" . $entity_guid; } return $url; } return false; } /** * Sets the URL handler for a particular entity type and subtype * * @param string $function_name The function to register * @param string $entity_type The entity type * @param string $entity_subtype The entity subtype * @return true|false Depending on success */ function register_entity_url_handler($function_name, $entity_type = "all", $entity_subtype = "all") { global $CONFIG; if (!is_callable($function_name)) return false; if (!isset($CONFIG->entity_url_handler)) { $CONFIG->entity_url_handler = array(); } if (!isset($CONFIG->entity_url_handler[$entity_type])) { $CONFIG->entity_url_handler[$entity_type] = array(); } $CONFIG->entity_url_handler[$entity_type][$entity_subtype] = $function_name; return true; } /** * Default Icon URL handler for entities. * This will attempt to find a default entity for the current view and return a url. This is registered at * a low priority so that other handlers will pick it up first. * * @param unknown_type $hook * @param unknown_type $entity_type * @param unknown_type $returnvalue * @param unknown_type $params */ function default_entity_url_hook($hook, $entity_type, $returnvalue, $params) { global $CONFIG; if ((!$returnvalue) && ($hook == 'entity:icon:url')) { $entity = $params['entity']; $type = $entity->type; $subtype = get_subtype_from_id($entity->subtype); $viewtype = $params['viewtype']; $size = $params['size']; $url = $CONFIG->url . "views/$viewtype/graphics/icons/$type/$subtype/$size.png"; if(!@getimagesize($url)) $url = $CONFIG->url . "views/$viewtype/graphics/icons/$type/default/$size.png"; if(!@getimagesize($url)) $url = $CONFIG->url . "views/$viewtype/graphics/icons/default/$size.png"; return $url; } } /** * Registers and entity type and subtype to return in search and other places. * A description in the elgg_echo languages file of the form item:type:subtype * is also expected. * * @param string $type The type of entity (object, site, user, group) * @param string $subtype The subtype to register (may be blank) * @return true|false Depending on success */ function register_entity_type($type, $subtype) { global $CONFIG; $type = strtolower($type); if (!in_array($type,array('object','site','group','user'))) return false; if (!isset($CONFIG->registered_entities)) $CONFIG->registered_entities = array(); $CONFIG->registered_entities[$type][] = $subtype; return true; } /** * Returns registered entity types and subtypes * * @see register_entity_type * * @param string $type The type of entity (object, site, user, group) or blank for all * @return array|false Depending on whether entities have been registered */ function get_registered_entity_types($type = '') { global $CONFIG; if (!isset($CONFIG->registered_entities)) return false; if (!empty($type)) $type = strtolower($type); if (!empty($type) && empty($CONFIG->registered_entities[$type])) return false; if (empty($type)) return $CONFIG->registered_entities; return $CONFIG->registered_entities[$type]; } /** * Determines whether or not the specified entity type and subtype have been registered in the system * * @param string $type The type of entity (object, site, user, group) * @param string $subtype The subtype (may be blank) * @return true|false Depending on whether or not the type has been registered */ function is_registered_entity_type($type, $subtype) { global $CONFIG; if (!isset($CONFIG->registered_entities)) return false; $type = strtolower($type); if (empty($CONFIG->registered_entities[$type])) return false; if (in_array($subtype, $CONFIG->registered_entities[$type])) return true; } /** * Page handler for generic entities view system * * @param array $page Page elements from pain page handler */ function entities_page_handler($page) { if (isset($page[0])) { global $CONFIG; set_input('guid',$page[0]); @include($CONFIG->path . "entities/index.php"); } } /** * Returns a viewable list of entities based on the registered types * * @see elgg_view_entity_list * * @param string $type The type of entity (eg "user", "object" etc) * @param string $subtype The arbitrary subtype of the entity * @param int $owner_guid The GUID of the owning user * @param int $limit The number of entities to display per page (default: 10) * @param true|false $fullview Whether or not to display the full view (default: true) * @param true|false $viewtypetoggle Whether or not to allow gallery view * @return string A viewable list of entities */ function list_registered_entities($owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false) { $typearray = array(); if ($object_types = get_registered_entity_types()) { foreach($object_types as $object_type => $subtype_array) { if (is_array($subtype_array) && sizeof($subtype_array)) foreach($subtype_array as $object_subtype) { $typearray[$object_type][] = $object_subtype; } } } $offset = (int) get_input('offset'); $count = get_entities($typearray, '', $owner_guid, "", $limit, $offset, true); $entities = get_entities($typearray, '', $owner_guid, "", $limit, $offset); return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle); } /** * Entities init function; establishes the page handler * */ function entities_init() { register_page_handler('view','entities_page_handler'); } /** Register the import hook */ register_plugin_hook("import", "all", "import_entity_plugin_hook", 0); /** Register the hook, ensuring entities are serialised first */ register_plugin_hook("export", "all", "export_entity_plugin_hook", 0); /** Hook to get certain named bits of volatile data about an entity */ register_plugin_hook('volatile', 'metadata', 'volatile_data_export_plugin_hook'); /** Hook for rendering a default icon for entities */ register_plugin_hook('entity:icon:url', 'all', 'default_entity_url_hook', 700); /** Register init system event **/ register_elgg_event_handler('init','system','entities_init'); ?>