diff options
Diffstat (limited to 'engine/lib/entities.php')
| -rw-r--r-- | engine/lib/entities.php | 3809 |
1 files changed, 1427 insertions, 2382 deletions
diff --git a/engine/lib/entities.php b/engine/lib/entities.php index 0fa08de58..4fcf1c657 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -1,1178 +1,142 @@ <?php /** - * Elgg entities. - * Functions to manage all elgg entities (sites, collections, objects and users). + * Procedural code for creating, loading, and modifying ElggEntity objects. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage DataModel.Entities + * @link http://docs.elgg.org/DataModel/Entities */ -/// Cache objects in order to minimise database access. -$ENTITY_CACHE = NULL; - -/// Cache subtype searches -$SUBTYPE_CACHE = NULL; - -/// Require the locatable interface TODO: Move this into start.php? -require_once('location.php'); - /** - * ElggEntity The elgg entity superclass - * This class holds methods for accessing the main entities table. + * Cache entities in memory once loaded. * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Core + * @global array $ENTITY_CACHE + * @access private */ -abstract class ElggEntity implements - Notable, // Calendar interface - Locatable, // Geocoding interface - 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; - - - /** - * 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; - - /** - * 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(); - } - if (!is_array($this->volatile)) { - $this->volatile = array(); - } - - $this->attributes['guid'] = ""; - $this->attributes['type'] = ""; - $this->attributes['subtype'] = ""; - - $this->attributes['owner_guid'] = get_loggedin_userid(); - $this->attributes['container_guid'] = get_loggedin_userid(); - - $this->attributes['site_guid'] = 0; - $this->attributes['access_id'] = ACCESS_PRIVATE; - $this->attributes['time_created'] = ""; - $this->attributes['time_updated'] = ""; - $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; - } - - /** - * 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. - * - * @todo Move "title" logic to applicable extending classes. - * - * @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; - } - - // strip out tags from title - if ($name == 'title') { - $value = strip_tags($value); - } - - $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); - } - } - - - /** - * Get a piece of volatile (non-persisted) data on this entity - */ - 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 - */ - public function setVolatileData($name, $value) { - if (!is_array($this->volatile)) { - $this->volatile = array(); - } - - $this->volatile[$name] = $value; - } - - - /** - * 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; - } - - /** - * Add a relationship. - * - * @param int $guid Relationship to link to. - * @param string $relationship The type of relationship. - */ - public function addRelationship($guid, $relationship) { - return add_entity_relationship($this->getGUID(), $relationship, $guid); - } - - /** - * Adds a private setting to this entity. - * - * @param $name - * @param $value - * @return unknown_type - */ - function setPrivateSetting($name, $value) { - return set_private_setting($this->getGUID(), $name, $value); - } - - /** - * Gets private setting for this entity - * - * @param $name - * @return unknown_type - */ - function getPrivateSetting($name) { - return get_private_setting($this->getGUID(), $name); - } - - /** - * Removes private setting for this entity. - * - * @param $name - * @return unknown_type - */ - function removePrivateSetting($name) { - return remove_private_setting($this->getGUID(), $name); - } - - /** - * 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 = 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; - } - - /** - * 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 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')); - } - - /** - * 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'), - $this->get('container_guid') - ); - } 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 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. - * - * @param string $reason Optional reason - * @param bool $recursive Recursively disable all contained entities? - */ - public function disable($reason = "", $recursive = true) { - return disable_entity($this->get('guid'), $reason, $recursive); - } - - /** - * Re-enable this entity. - */ - public function enable() { - return enable_entity($this->get('guid')); - } - - /** - * Is this entity enabled? - * - * @return boolean - */ - public function isEnabled() { - if ($this->enabled == 'yes') { - return true; - } - - return false; - } - - /** - * Delete this entity. - */ - public function delete() { - return delete_entity($this->get('guid')); - } - - // LOCATABLE INTERFACE ///////////////////////////////////////////////////////////// - - /** Interface to set the location */ - public function setLocation($location) { - $location = sanitise_string($location); - - $this->location = $location; - - return true; - } - - /** - * Set latitude and longitude tags for a given entity. - * - * @param float $lat - * @param float $long - */ - public function setLatLong($lat, $long) { - $lat = sanitise_string($lat); - $long = sanitise_string($long); - - $this->set('geo:lat', $lat); - $this->set('geo:long', $long); - - return true; - } - - /** - * Get the contents of the ->geo:lat field. - * - */ - public function getLatitude() { - return $this->get('geo:lat'); - } - - /** - * Get the contents of the ->geo:lat field. - * - */ - public function getLongitude() { - return $this->get('geo:long'); - } - - /** - * Get the ->location metadata. - * - */ - public function getLocation() { - return $this->get('location'); - } - - // NOTABLE INTERFACE /////////////////////////////////////////////////////////////// - - /** - * Calendar functionality. - * This function sets the time of an object on a calendar listing. - * - * @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. - */ - 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; +global $ENTITY_CACHE; +$ENTITY_CACHE = array(); - return true; - } - - /** - * Return the start timestamp. - */ - public function getCalendarStartTime() { - return (int)$this->calendar_start; - } - - /** - * Return the end timestamp. - */ - public function getCalendarEndTime() { - return (int)$this->calendar_end; - } - - // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// - - /** - * Return an array of fields which can be exported. - */ - public function getExportableValues() { - return array( - 'guid', - 'type', - 'subtype', - 'time_created', - 'container_guid', - 'owner_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) - */ - 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, 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'] = get_loggedin_userid(); // 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; - } - } +/** + * GUIDs of entities banned from the entity cache (during this request) + * + * @global array $ENTITY_CACHE_DISABLED_GUIDS + * @access private + */ +global $ENTITY_CACHE_DISABLED_GUIDS; +$ENTITY_CACHE_DISABLED_GUIDS = array(); - function offsetGet($key) { - if ( array_key_exists($key, $this->attributes) ) { - return $this->attributes[$key]; - } - } +/** + * Cache subtypes and related class names. + * + * @global array|null $SUBTYPE_CACHE array once populated from DB, initially null + * @access private + */ +global $SUBTYPE_CACHE; +$SUBTYPE_CACHE = null; - function offsetUnset($key) { - if ( array_key_exists($key, $this->attributes) ) { - $this->attributes[$key] = ""; // Full unsetting is dangerious for our objects - } - } +/** + * Remove this entity from the entity cache and make sure it is not re-added + * + * @param int $guid The entity guid + * + * @access private + * @todo this is a workaround until #5604 can be implemented + */ +function _elgg_disable_caching_for_entity($guid) { + global $ENTITY_CACHE_DISABLED_GUIDS; - function offsetExists($offset) { - return array_key_exists($offset, $this->attributes); - } + _elgg_invalidate_cache_for_entity($guid); + $ENTITY_CACHE_DISABLED_GUIDS[$guid] = true; } /** - * Initialise the entity cache. + * Allow this entity to be stored in the entity cache + * + * @param int $guid The entity guid + * + * @access private */ -function initialise_entity_cache() { - global $ENTITY_CACHE; +function _elgg_enable_caching_for_entity($guid) { + global $ENTITY_CACHE_DISABLED_GUIDS; - if (!$ENTITY_CACHE) { - //select_default_memcache('entity_cache'); // TODO: Replace with memcache? - $ENTITY_CACHE = array(); - } + unset($ENTITY_CACHE_DISABLED_GUIDS[$guid]); } /** - * Invalidate this class' entry in the cache. + * Invalidate this class's entry in the cache. * - * @param int $guid The guid + * @param int $guid The entity guid + * + * @return void + * @access private */ -function invalidate_cache_for_entity($guid) { +function _elgg_invalidate_cache_for_entity($guid) { global $ENTITY_CACHE; $guid = (int)$guid; unset($ENTITY_CACHE[$guid]); - //$ENTITY_CACHE->delete($guid); + + elgg_get_metadata_cache()->clear($guid); } /** * Cache an entity. * + * Stores an entity in $ENTITY_CACHE; + * * @param ElggEntity $entity Entity to cache + * + * @return void + * @see _elgg_retrieve_cached_entity() + * @see _elgg_invalidate_cache_for_entity() + * @access private + * @todo Use an ElggCache object */ -function cache_entity(ElggEntity $entity) { - global $ENTITY_CACHE; +function _elgg_cache_entity(ElggEntity $entity) { + global $ENTITY_CACHE, $ENTITY_CACHE_DISABLED_GUIDS; - $ENTITY_CACHE[$entity->guid] = $entity; + // Don't cache non-plugin entities while access control is off, otherwise they could be + // exposed to users who shouldn't see them when control is re-enabled. + if (!($entity instanceof ElggPlugin) && elgg_get_ignore_access()) { + return; + } + + $guid = $entity->getGUID(); + if (isset($ENTITY_CACHE_DISABLED_GUIDS[$guid])) { + return; + } + + // Don't store too many or we'll have memory problems + // @todo Pick a less arbitrary limit + if (count($ENTITY_CACHE) > 256) { + $random_guid = array_rand($ENTITY_CACHE); + + unset($ENTITY_CACHE[$random_guid]); + + // Purge separate metadata cache. Original idea was to do in entity destructor, but that would + // have caused a bunch of unnecessary purges at every shutdown. Doing it this way we have no way + // to know that the expunged entity will be GCed (might be another reference living), but that's + // OK; the metadata will reload if necessary. + elgg_get_metadata_cache()->clear($random_guid); + } + + $ENTITY_CACHE[$guid] = $entity; } /** * Retrieve a entity from the cache. * * @param int $guid The guid + * + * @return ElggEntity|bool false if entity not cached, or not fully loaded + * @see _elgg_cache_entity() + * @see _elgg_invalidate_cache_for_entity() + * @access private */ -function retrieve_cached_entity($guid) { +function _elgg_retrieve_cached_entity($guid) { global $ENTITY_CACHE; - $guid = (int)$guid; - if (isset($ENTITY_CACHE[$guid])) { if ($ENTITY_CACHE[$guid]->isFullyLoaded()) { return $ENTITY_CACHE[$guid]; @@ -1183,195 +147,312 @@ function retrieve_cached_entity($guid) { } /** - * As retrieve_cached_entity, but returns the result as a stdClass (compatible with load functions that - * expect a database row.) + * Return the id for a given subtype. * - * @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. + * ElggEntity objects have a type and a subtype. Subtypes + * are defined upon creation and cannot be changed. * - * TODO: Move to a nicer place? + * Plugin authors generally don't need to use this function + * unless writing their own SQL queries. Use {@link ElggEntity::getSubtype()} + * to return the string subtype. * - * @param string $type - * @param string $subtype + * @warning {@link ElggEntity::subtype} returns the ID. You probably want + * {@link ElggEntity::getSubtype()} instead! + * + * @internal Subtypes are stored in the entity_subtypes table. There is a foreign + * key in the entities table. + * + * @param string $type Type + * @param string $subtype Subtype + * + * @return int Subtype ID + * @link http://docs.elgg.org/DataModel/Entities/Subtypes + * @see get_subtype_from_id() + * @access private */ function get_subtype_id($type, $subtype) { - global $CONFIG, $SUBTYPE_CACHE; - - $type = sanitise_string($type); - $subtype = sanitise_string($subtype); + global $SUBTYPE_CACHE; - if ($subtype=="") { - //return $subtype; - return FALSE; + if (!$subtype) { + return false; } - // 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) { - if (!$SUBTYPE_CACHE) { - //select_default_memcache('subtype_cache'); - $SUBTYPE_CACHE = array(); - } + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } - $SUBTYPE_CACHE[$result->id] = $result; + // use the cache before hitting database + $result = _elgg_retrieve_cached_subtype($type, $subtype); + if ($result !== null) { return $result->id; } - return FALSE; + return false; } /** - * For a given subtype ID, return its identifier text. + * Return string name for a given subtype ID. * - * TODO: Move to a nicer place? + * @param int $subtype_id Subtype ID * - * @param int $subtype_id + * @return string|false Subtype name, false if subtype not found + * @link http://docs.elgg.org/DataModel/Entities/Subtypes + * @see get_subtype_from_id() + * @access private */ function get_subtype_from_id($subtype_id) { - global $CONFIG, $SUBTYPE_CACHE; - - $subtype_id = (int)$subtype_id; + global $SUBTYPE_CACHE; if (!$subtype_id) { return false; } + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + 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) { - if (!$SUBTYPE_CACHE) { - //select_default_memcache('subtype_cache'); - $SUBTYPE_CACHE = array(); - } + return false; +} - $SUBTYPE_CACHE[$subtype_id] = $result; - return $result->subtype; +/** + * Retrieve subtype from the cache. + * + * @param string $type + * @param string $subtype + * @return stdClass|null + * + * @access private + */ +function _elgg_retrieve_cached_subtype($type, $subtype) { + global $SUBTYPE_CACHE; + + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); } - return false; + foreach ($SUBTYPE_CACHE as $obj) { + if ($obj->type === $type && $obj->subtype === $subtype) { + return $obj; + } + } + return null; } /** - * This function tests to see if a subtype has a registered class handler. + * Fetch all suptypes from DB to local cache. * - * @param string $type The type - * @param string $subtype The subtype - * @return a class name or null + * @access private */ -function get_subtype_class($type, $subtype) { +function _elgg_populate_subtype_cache() { global $CONFIG, $SUBTYPE_CACHE; + + $results = get_data("SELECT * FROM {$CONFIG->dbprefix}entity_subtypes"); + + $SUBTYPE_CACHE = array(); + foreach ($results as $row) { + $SUBTYPE_CACHE[$row->id] = $row; + } +} - $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) { - if (!$SUBTYPE_CACHE) { - //select_default_memcache('subtype_cache'); - $SUBTYPE_CACHE = array(); - } +/** + * Return the class name for a registered type and subtype. + * + * Entities can be registered to always be loaded as a certain class + * with add_subtype() or update_subtype(). This function returns the class + * name if found and NULL if not. + * + * @param string $type The type + * @param string $subtype The subtype + * + * @return string|null a class name or null + * @see get_subtype_from_id() + * @see get_subtype_class_from_id() + * @access private + */ +function get_subtype_class($type, $subtype) { + global $SUBTYPE_CACHE; - $SUBTYPE_CACHE[$result->id] = $result; - return $result->class; + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + + // use the cache before going to the database + $obj = _elgg_retrieve_cached_subtype($type, $subtype); + if ($obj) { + return $obj->class; } - return NULL; + return null; } /** - * This function tests to see if a subtype has a registered class handler by its id. + * Returns the class name for a subtype id. * - * @param int $subtype_id The subtype - * @return a class name or null + * @param int $subtype_id The subtype id + * + * @return string|null + * @see get_subtype_class() + * @see get_subtype_from_id() + * @access private */ function get_subtype_class_from_id($subtype_id) { - global $CONFIG, $SUBTYPE_CACHE; - - $subtype_id = (int)$subtype_id; + global $SUBTYPE_CACHE; if (!$subtype_id) { - return false; + return null; } + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + 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"); + return null; +} - if ($result) { - if (!$SUBTYPE_CACHE) { - //select_default_memcache('subtype_cache'); - $SUBTYPE_CACHE = array(); - } - $SUBTYPE_CACHE[$subtype_id] = $result; - return $result->class; +/** + * Register ElggEntities with a certain type and subtype to be loaded as a specific class. + * + * By default entities are loaded as one of the 4 parent objects: site, user, object, or group. + * If you subclass any of these you can register the classname with add_subtype() so + * it will be loaded as that class automatically when retrieved from the database with + * {@link get_entity()}. + * + * @warning This function cannot be used to change the class for a type-subtype pair. + * Use update_subtype() for that. + * + * @param string $type The type you're subtyping (site, user, object, or group) + * @param string $subtype The subtype + * @param string $class Optional class name for the object + * + * @return int + * @link http://docs.elgg.org/Tutorials/Subclasses + * @link http://docs.elgg.org/DataModel/Entities + * @see update_subtype() + * @see remove_subtype() + * @see get_entity() + */ +function add_subtype($type, $subtype, $class = "") { + global $CONFIG, $SUBTYPE_CACHE; + + if (!$subtype) { + return 0; } - return NULL; + $id = get_subtype_id($type, $subtype); + + if (!$id) { + // In cache we store non-SQL-escaped strings because that's what's returned by query + $cache_obj = (object) array( + 'type' => $type, + 'subtype' => $subtype, + 'class' => $class, + ); + + $type = sanitise_string($type); + $subtype = sanitise_string($subtype); + $class = sanitise_string($class); + + $id = insert_data("INSERT INTO {$CONFIG->dbprefix}entity_subtypes" + . " (type, subtype, class) VALUES ('$type', '$subtype', '$class')"); + + // add entry to cache + $cache_obj->id = $id; + $SUBTYPE_CACHE[$id] = $cache_obj; + } + + return $id; } /** - * This function will register a new subtype, returning its ID as required. + * Removes a registered ElggEntity type, subtype, and classname. * - * @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) + * @warning You do not want to use this function. If you want to unregister + * a class for a subtype, use update_subtype(). Using this function will + * permanently orphan all the objects created with the specified subtype. + * + * @param string $type Type + * @param string $subtype Subtype + * + * @return bool + * @see add_subtype() + * @see update_subtype() */ -function add_subtype($type, $subtype, $class = "") { +function remove_subtype($type, $subtype) { 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; - } + return delete_data("DELETE FROM {$CONFIG->dbprefix}entity_subtypes" + . " WHERE type = '$type' AND subtype = '$subtype'"); +} + +/** + * Update a registered ElggEntity type, subtype, and class name + * + * @param string $type Type + * @param string $subtype Subtype + * @param string $class Class name to use when loading this entity + * + * @return bool + */ +function update_subtype($type, $subtype, $class = '') { + global $CONFIG, $SUBTYPE_CACHE; $id = get_subtype_id($type, $subtype); + if (!$id) { + return false; + } - if ($id==0) { - return insert_data("insert into {$CONFIG->dbprefix}entity_subtypes (type, subtype, class) values ('$type','$subtype','$class')"); + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); } - return $id; + $unescaped_class = $class; + + $type = sanitise_string($type); + $subtype = sanitise_string($subtype); + $class = sanitise_string($class); + + $success = update_data("UPDATE {$CONFIG->dbprefix}entity_subtypes + SET type = '$type', subtype = '$subtype', class = '$class' + WHERE id = $id + "); + + if ($success && isset($SUBTYPE_CACHE[$id])) { + $SUBTYPE_CACHE[$id]->class = $unescaped_class; + } + + return $success; } /** - * Update an existing entity. + * Update an entity in the database. + * + * There are 4 basic entity types: site, user, object, and group. + * All entities are split between two tables: the entities table and their type table. + * + * @warning Plugin authors should never call this directly. Use ->save() instead. * - * @param int $guid - * @param int $owner_guid - * @param int $access_id - * @param int $container_guid + * @param int $guid The guid of the entity to update + * @param int $owner_guid The new owner guid + * @param int $access_id The new access id + * @param int $container_guid The new container guid + * @param int $time_created The time creation timestamp + * + * @return bool + * @throws InvalidParameterException + * @access private */ -function update_entity($guid, $owner_guid, $access_id, $container_guid = null) { +function update_entity($guid, $owner_guid, $access_id, $container_guid = null, $time_created = null) { global $CONFIG, $ENTITY_CACHE; $guid = (int)$guid; @@ -1385,12 +466,25 @@ function update_entity($guid, $owner_guid, $access_id, $container_guid = null) { $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', container_guid='$container_guid', time_updated='$time' WHERE guid=$guid"); + if ($time_created == null) { + $time_created = $entity->time_created; + } else { + $time_created = (int) $time_created; + } + + if ($access_id == ACCESS_DEFAULT) { + throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); + } + + if ($entity && $entity->canEdit()) { + if (elgg_trigger_event('update', $entity->type, $entity)) { + $ret = update_data("UPDATE {$CONFIG->dbprefix}entities + set owner_guid='$owner_guid', access_id='$access_id', + container_guid='$container_guid', time_created='$time_created', + time_updated='$time' WHERE guid=$guid"); if ($entity instanceof ElggObject) { - update_river_access_by_object($guid,$access_id); + update_river_access_by_object($guid, $access_id); } // If memcache is available then delete this entry from the cache @@ -1399,11 +493,11 @@ function update_entity($guid, $owner_guid, $access_id, $container_guid = null) { $newentity_cache = new ElggMemcache('new_entity_cache'); } if ($newentity_cache) { - $new_entity = $newentity_cache->delete($guid); + $newentity_cache->delete($guid); } // Handle cases where there was no error BUT no rows were updated! - if ($ret===false) { + if ($ret === false) { return false; } @@ -1413,26 +507,38 @@ function update_entity($guid, $owner_guid, $access_id, $container_guid = null) { } /** - * Determine whether a given user is able to write to a given container. + * Determine if a given user can write to an entity container. + * + * An entity can be a container for any other entity by setting the + * container_guid. container_guid can differ from owner_guid. + * + * A plugin hook container_permissions_check:$entity_type is emitted to allow granular + * access controls in plugins. * - * @param int $user_guid The user guid, or 0 for get_loggedin_userid() - * @param int $container_guid The container, or 0 for the current page owner. + * @param int $user_guid The user guid, or 0 for logged in user + * @param int $container_guid The container, or 0 for the current page owner. + * @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 + * @link http://docs.elgg.org/DataModel/Containers */ -function can_write_to_container($user_guid = 0, $container_guid = 0, $entity_type = 'all') { - global $CONFIG; - +function can_write_to_container($user_guid = 0, $container_guid = 0, $type = 'all', $subtype = 'all') { $user_guid = (int)$user_guid; $user = get_entity($user_guid); if (!$user) { - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); } $container_guid = (int)$container_guid; if (!$container_guid) { - $container_guid = page_owner(); + $container_guid = elgg_get_page_owner_guid(); } + + $return = false; + if (!$container_guid) { - return true; + $return = true; } $container = get_entity($container_guid); @@ -1440,43 +546,62 @@ function can_write_to_container($user_guid = 0, $container_guid = 0, $entity_typ if ($container) { // If the user can edit the container, they can also write to it if ($container->canEdit($user_guid)) { - return true; + $return = true; } - // Basics, see if the user is a member of the group. - if ($user && $container instanceof ElggGroup) { - if (!$container->isMember($user)) { - return false; - } else { - return true; + // If still not approved, see if the user is a member of the group + // @todo this should be moved to the groups plugin/library + if (!$return && $user && $container instanceof ElggGroup) { + /* @var ElggGroup $container */ + if ($container->isMember($user)) { + $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; + // See if anyone else has anything to say + return elgg_trigger_plugin_hook( + 'container_permissions_check', + $type, + array( + 'container' => $container, + 'user' => $user, + 'subtype' => $subtype + ), + $return); } /** - * 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 + * Create a new entry in the entities table. + * + * Saves the base information in the entities table for the entity. Saving + * the type information is handled in the calling class method. + * + * @warning Plugin authors should never call this directly. Always use entity objects. + * + * @warning Entities must have an entry in both the entities table and their type table + * or they will throw an exception when loaded. + * + * @param string $type The type of the entity (site, user, object, group). + * @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. 0 for current. + * @param int $container_guid The container GUID + * + * @return int|false The new entity's GUID, or false on failure + * @throws InvalidParameterException + * @link http://docs.elgg.org/DataModel/Entities + * @access private */ -function create_entity($type, $subtype, $owner_guid, $access_id, $site_guid = 0, $container_guid = 0) { +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); + $subtype_id = 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; @@ -1485,31 +610,46 @@ function create_entity($type, $subtype, $owner_guid, $access_id, $site_guid = 0, if ($container_guid == 0) { $container_guid = $owner_guid; } + $access_id = (int)$access_id; + if ($access_id == ACCESS_DEFAULT) { + throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); + } - $user = get_loggedin_user(); - if (!can_write_to_container($user->guid, $owner_guid, $type)) { + $user_guid = elgg_get_logged_in_user_guid(); + if (!can_write_to_container($user_guid, $owner_guid, $type, $subtype)) { return false; } if ($owner_guid != $container_guid) { - if (!can_write_to_container($user->guid, $container_guid, $type)) { + if (!can_write_to_container($user_guid, $container_guid, $type, $subtype)) { return false; } } - if ($type=="") { + 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)"); + (type, subtype, owner_guid, site_guid, container_guid, + access_id, time_created, time_updated, last_action) + values + ('$type',$subtype_id, $owner_guid, $site_guid, $container_guid, + $access_id, $time, $time, $time)"); } /** - * Retrieve the entity details for a specific GUID, returning it as a stdClass db row. + * Returns a database row from the entities table. + * + * @tip Use get_entity() to return the fully loaded entity. * - * You will only get an object if a) it exists, b) you have access to it. + * @warning This will only return results if a) it exists, b) you have access to it. + * see {@link get_access_sql_suffix()}. * * @param int $guid The GUID of the object to extract + * + * @return stdClass|false + * @link http://docs.elgg.org/DataModel/Entities + * @see entity_row_to_elggstar() + * @access private */ function get_entity_as_row($guid) { global $CONFIG; @@ -1526,6 +666,19 @@ function get_entity_as_row($guid) { /** * Create an Elgg* object from a given entity row. + * + * Handles loading all tables into the correct class. + * + * @param stdClass $row The row of the entry in the entities table. + * + * @return ElggEntity|false + * @link http://docs.elgg.org/DataModel/Entities + * @see get_entity_as_row() + * @see add_subtype() + * @see get_entity() + * @access private + * + * @throws ClassException|InstallationException */ function entity_row_to_elggstar($row) { if (!($row instanceof stdClass)) { @@ -1550,31 +703,39 @@ function entity_row_to_elggstar($row) { return $new_entity; } + // load class for entity if one is registered $classname = get_subtype_class_from_id($row->subtype); - if ($classname!="") { + if ($classname != "") { if (class_exists($classname)) { $new_entity = new $classname($row); if (!($new_entity instanceof ElggEntity)) { - throw new ClassException(sprintf(elgg_echo('ClassException:ClassnameNotClass'), $classname, 'ElggEntity')); + $msg = elgg_echo('ClassException:ClassnameNotClass', array($classname, 'ElggEntity')); + throw new ClassException($msg); } - } - else { - error_log(sprintf(elgg_echo('ClassNotFoundException:MissingClass'), $classname)); + } else { + error_log(elgg_echo('ClassNotFoundException:MissingClass', array($classname))); } } - else { + + if (!$new_entity) { + //@todo Make this into a function switch ($row->type) { case 'object' : - $new_entity = new ElggObject($row); break; + $new_entity = new ElggObject($row); + break; case 'user' : - $new_entity = new ElggUser($row); break; + $new_entity = new ElggUser($row); + break; case 'group' : - $new_entity = new ElggGroup($row); break; + $new_entity = new ElggGroup($row); + break; case 'site' : - $new_entity = new ElggSite($row); break; + $new_entity = new ElggSite($row); + break; default: - throw new InstallationException(sprintf(elgg_echo('InstallationException:TypeNotSupported'), $row->type)); + $msg = elgg_echo('InstallationException:TypeNotSupported', array($row->type)); + throw new InstallationException($msg); } } @@ -1587,55 +748,144 @@ function entity_row_to_elggstar($row) { } /** - * Return the entity for a given guid as the correct object. + * Loads and returns an entity object from a guid. + * * @param int $guid The GUID of the entity - * @return a child of ElggEntity appropriate for the type. + * + * @return ElggEntity The correct Elgg or custom object based upon entity type and subtype + * @link http://docs.elgg.org/DataModel/Entities */ function get_entity($guid) { - static $newentity_cache; - $new_entity = false; - if ((!$newentity_cache) && (is_memcache_available())) { - $newentity_cache = new ElggMemcache('new_entity_cache'); + // This should not be a static local var. Notice that cache writing occurs in a completely + // different instance outside this function. + // @todo We need a single Memcache instance with a shared pool of namespace wrappers. This function would pull an instance from the pool. + static $shared_cache; + + // We could also use: if (!(int) $guid) { return FALSE }, + // but that evaluates to a false positive for $guid = TRUE. + // This is a bit slower, but more thorough. + if (!is_numeric($guid) || $guid === 0 || $guid === '0') { + return false; + } + + // Check local cache first + $new_entity = _elgg_retrieve_cached_entity($guid); + if ($new_entity) { + return $new_entity; } - if ($newentity_cache) { - $new_entity = $newentity_cache->load($guid); + // Check shared memory cache, if available + if (null === $shared_cache) { + if (is_memcache_available()) { + $shared_cache = new ElggMemcache('new_entity_cache'); + } else { + $shared_cache = false; + } } - if ($new_entity) { - return $new_entity; + // until ACLs in memcache, DB query is required to determine access + $entity_row = get_entity_as_row($guid); + if (!$entity_row) { + return false; + } + + if ($shared_cache) { + $cached_entity = $shared_cache->load($guid); + // @todo store ACLs in memcache https://github.com/elgg/elgg/issues/3018#issuecomment-13662617 + if ($cached_entity) { + // @todo use ACL and cached entity access_id to determine if user can see it + return $cached_entity; + } } - return entity_row_to_elggstar(get_entity_as_row($guid)); + // don't let incomplete entities cause fatal exceptions + try { + $new_entity = entity_row_to_elggstar($entity_row); + } catch (IncompleteEntityException $e) { + return false; + } + + if ($new_entity) { + _elgg_cache_entity($new_entity); + } + return $new_entity; } +/** + * Does an entity exist? + * + * This function checks for the existence of an entity independent of access + * permissions. It is useful for situations when a user cannot access an entity + * and it must be determined whether entity has been deleted or the access level + * has changed. + * + * @param int $guid The GUID of the entity + * + * @return bool + * @since 1.8.0 + */ +function elgg_entity_exists($guid) { + global $CONFIG; + + $guid = sanitize_int($guid); + + $query = "SELECT count(*) as total FROM {$CONFIG->dbprefix}entities WHERE guid = $guid"; + $result = get_data_row($query); + if ($result->total == 0) { + return false; + } else { + return true; + } +} /** - * Get all entities. + * Returns an array of entities with optional filtering. + * + * Entities are the basic unit of storage in Elgg. This function + * provides the simplest way to get an array of entities. There + * are many options available that can be passed to filter + * what sorts of entities are returned. + * + * @tip To output formatted strings of entities, use {@link elgg_list_entities()} and + * its cousins. + * + * @tip Plural arguments can be written as singular if only specifying a + * single element. ('type' => 'object' vs 'types' => array('object')). * * @param array $options Array in format: * - * types => NULL|STR entity type + * types => NULL|STR entity type (type IN ('type1', 'type2') + * Joined with subtypes by AND. See below) + * + * subtypes => NULL|STR entity subtype (SQL: subtype IN ('subtype1', 'subtype2)) + * Use ELGG_ENTITIES_NO_VALUE for no subtype. * - * subtypes => NULL|STR entity subtype + * type_subtype_pairs => NULL|ARR (array('type' => 'subtype')) + * (type = '$type' AND subtype = '$subtype') pairs * - * type_subtype_pairs => NULL|ARR (type = '$type' AND subtype = '$subtype') pairs + * guids => NULL|ARR Array of entity guids * - * owner_guids => NULL|INT entity guid + * owner_guids => NULL|ARR Array of owner guids * - * container_guids => NULL|INT container_guid + * container_guids => NULL|ARR Array of container_guids * - * site_guids => NULL (current_site)|INT site_guid + * site_guids => NULL (current_site)|ARR Array of site_guid * * order_by => NULL (time_created desc)|STR SQL order by clause * - * limit => NULL (10)|INT SQL limit clause + * reverse_order_by => BOOL Reverse the default order by clause + * + * limit => NULL (10)|INT SQL limit clause (0 means no limit) * * offset => NULL (0)|INT SQL offset clause * - * time_lower => NULL|INT Time lower boundary in epoch time + * created_time_lower => NULL|INT Created time lower boundary in epoch time + * + * created_time_upper => NULL|INT Created time upper boundary in epoch time * - * time_upper => NULL|INT Time upper boundary in epoch time + * modified_time_lower => NULL|INT Modified time lower boundary in epoch time + * + * modified_time_upper => NULL|INT Modified time upper boundary in epoch time * * count => TRUE|FALSE return a count instead of entities * @@ -1643,43 +893,64 @@ function get_entity($guid) { * * joins => array() Additional joins * - * @return array + * callback => string A callback function to pass each row through + * + * @return mixed If count, int. If not count, array. false on errors. + * @since 1.7.0 + * @see elgg_get_entities_from_metadata() + * @see elgg_get_entities_from_relationship() + * @see elgg_get_entities_from_access_id() + * @see elgg_get_entities_from_annotations() + * @see elgg_list_entities() + * @link http://docs.elgg.org/DataModel/Entities/Getters */ function elgg_get_entities(array $options = array()) { global $CONFIG; - //@todo allow use of singular types that rewrite to plural ones. $defaults = array( - 'type' => NULL, - 'types' => NULL, - 'subtypes' => NULL, - 'subtype' => NULL, - 'type_subtype_pairs' => NULL, - 'owner_guids' => NULL, - 'owner_guid' => NULL, - 'container_guids' => NULL, - 'container_guid' => NULL, - 'site_guids' => $CONFIG->site_guid, - 'site_guid' => NULL, - - 'modified_time_lower' => NULL, - 'modified_time_upper' => NULL, - 'created_time_lower' => NULL, - 'created_time_upper' => NULL, - - 'order_by' => 'e.time_created desc', - 'group_by' => NULL, - 'limit' => 10, - 'offset' => 0, - 'count' => FALSE, - 'selects' => array(), - 'wheres' => array(), - 'joins' => array() + 'types' => ELGG_ENTITIES_ANY_VALUE, + 'subtypes' => ELGG_ENTITIES_ANY_VALUE, + 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE, + + 'guids' => ELGG_ENTITIES_ANY_VALUE, + 'owner_guids' => ELGG_ENTITIES_ANY_VALUE, + 'container_guids' => ELGG_ENTITIES_ANY_VALUE, + 'site_guids' => $CONFIG->site_guid, + + 'modified_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'modified_time_upper' => ELGG_ENTITIES_ANY_VALUE, + 'created_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'created_time_upper' => ELGG_ENTITIES_ANY_VALUE, + + 'reverse_order_by' => false, + 'order_by' => 'e.time_created desc', + 'group_by' => ELGG_ENTITIES_ANY_VALUE, + 'limit' => 10, + 'offset' => 0, + 'count' => FALSE, + 'selects' => array(), + 'wheres' => array(), + 'joins' => array(), + + 'callback' => 'entity_row_to_elggstar', + + '__ElggBatch' => null, ); $options = array_merge($defaults, $options); - $singulars = array('type', 'subtype', 'owner_guid', 'container_guid', 'site_guid'); + // can't use helper function with type_subtype_pair because + // it's already an array...just need to merge it + if (isset($options['type_subtype_pair'])) { + if (isset($options['type_subtype_pairs'])) { + $options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'], + $options['type_subtype_pair']); + } else { + $options['type_subtype_pairs'] = $options['type_subtype_pair']; + } + } + + $singulars = array('type', 'subtype', 'guid', 'owner_guid', 'container_guid', 'site_guid'); $options = elgg_normalise_plural_options_array($options, $singulars); // evaluate where clauses @@ -1689,16 +960,17 @@ function elgg_get_entities(array $options = array()) { $wheres = $options['wheres']; - $wheres[] = elgg_get_entity_type_subtype_where_sql('e', $options['types'], $options['subtypes'], $options['type_subtype_pairs']); - $wheres[] = elgg_get_entity_site_where_sql('e', $options['site_guids']); - $wheres[] = elgg_get_entity_owner_where_sql('e', $options['owner_guids']); - $wheres[] = elgg_get_entity_container_where_sql('e', $options['container_guids']); + $wheres[] = elgg_get_entity_type_subtype_where_sql('e', $options['types'], + $options['subtypes'], $options['type_subtype_pairs']); + + $wheres[] = elgg_get_guid_based_where_sql('e.guid', $options['guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']); + $wheres[] = elgg_get_entity_time_where_sql('e', $options['created_time_upper'], $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']); - // remove identical where clauses - $wheres = array_unique($wheres); - // see if any functions failed // remove empty strings on successful functions foreach ($wheres as $i => $where) { @@ -1709,6 +981,9 @@ function elgg_get_entities(array $options = array()) { } } + // remove identical where clauses + $wheres = array_unique($wheres); + // evaluate join clauses if (!is_array($options['joins'])) { $options['joins'] = array($options['joins']); @@ -1729,7 +1004,7 @@ function elgg_get_entities(array $options = array()) { if ($options['selects']) { $selects = ''; foreach ($options['selects'] as $select) { - $selects = ", $select"; + $selects .= ", $select"; } } else { $selects = ''; @@ -1755,123 +1030,169 @@ function elgg_get_entities(array $options = array()) { // Add access controls $query .= get_access_sql_suffix('e'); + + // reverse order by + if ($options['reverse_order_by']) { + $options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by']); + } + if (!$options['count']) { - if ($group_by = sanitise_string($options['group_by'])) { - $query .= " GROUP BY $group_by"; + if ($options['group_by']) { + $query .= " GROUP BY {$options['group_by']}"; } - if ($order_by = sanitise_string($options['order_by'])) { - $query .= " ORDER BY $order_by"; + if ($options['order_by']) { + $query .= " ORDER BY {$options['order_by']}"; } if ($options['limit']) { - $limit = sanitise_int($options['limit']); - $offset = sanitise_int($options['offset']); + $limit = sanitise_int($options['limit'], false); + $offset = sanitise_int($options['offset'], false); $query .= " LIMIT $offset, $limit"; } - $dt = get_data($query, "entity_row_to_elggstar"); + if ($options['callback'] === 'entity_row_to_elggstar') { + $dt = _elgg_fetch_entities_from_sql($query, $options['__ElggBatch']); + } else { + $dt = get_data($query, $options['callback']); + } + + if ($dt) { + // populate entity and metadata caches + $guids = array(); + foreach ($dt as $item) { + // A custom callback could result in items that aren't ElggEntity's, so check for them + if ($item instanceof ElggEntity) { + _elgg_cache_entity($item); + // plugins usually have only settings + if (!$item instanceof ElggPlugin) { + $guids[] = $item->guid; + } + } + } + // @todo Without this, recursive delete fails. See #4568 + reset($dt); - //@todo normalize this to array() + if ($guids) { + elgg_get_metadata_cache()->populateFromEntities($guids); + } + } return $dt; } else { $total = get_data_row($query); - return $total->total; + return (int)$total->total; } } /** - * @deprecated 1.7. Use elgg_get_entities(). - * @param $type - * @param $subtype - * @param $owner_guid - * @param $order_by - * @param $limit - * @param $offset - * @param $count - * @param $site_guid - * @param $container_guid - * @param $timelower - * @param $timeupper - * @return unknown_type + * Return entities from an SQL query generated by elgg_get_entities. + * + * @param string $sql + * @param ElggBatch $batch + * @return ElggEntity[] + * + * @access private + * @throws LogicException */ -function get_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, -$count = false, $site_guid = 0, $container_guid = null, $timelower = 0, $timeupper = 0) { - - elgg_log('get_entities() was deprecated in 1.7 by elgg_get_entities()!', 'WARNING'); - // rewrite owner_guid to container_guid to emulate old functionality - $container_guid = $owner_guid; - $owner_guid = NULL; - - $options = array(); - if ($type) { - if (is_array($type)) { - $options['types'] = $type; - } else { - $options['type'] = $type; - } - } +function _elgg_fetch_entities_from_sql($sql, ElggBatch $batch = null) { + static $plugin_subtype; + if (null === $plugin_subtype) { + $plugin_subtype = get_subtype_id('object', 'plugin'); + } + + // Keys are types, values are columns that, if present, suggest that the secondary + // table is already JOINed + $types_to_optimize = array( + 'object' => 'title', + 'user' => 'password', + 'group' => 'name', + ); - if ($subtype) { - if (is_array($subtype)) { - $options['subtypes'] = $subtype; - } else { - $options['subtype'] = $subtype; + $rows = get_data($sql); + + // guids to look up in each type + $lookup_types = array(); + // maps GUIDs to the $rows key + $guid_to_key = array(); + + if (isset($rows[0]->type, $rows[0]->subtype) + && $rows[0]->type === 'object' + && $rows[0]->subtype == $plugin_subtype) { + // Likely the entire resultset is plugins, which have already been optimized + // to JOIN the secondary table. In this case we allow retrieving from cache, + // but abandon the extra queries. + $types_to_optimize = array(); + } + + // First pass: use cache where possible, gather GUIDs that we're optimizing + foreach ($rows as $i => $row) { + if (empty($row->guid) || empty($row->type)) { + throw new LogicException('Entity row missing guid or type'); + } + if ($entity = _elgg_retrieve_cached_entity($row->guid)) { + $rows[$i] = $entity; + continue; + } + if (isset($types_to_optimize[$row->type])) { + // check if row already looks JOINed. + if (isset($row->{$types_to_optimize[$row->type]})) { + // Row probably already contains JOINed secondary table. Don't make another query just + // to pull data that's already there + continue; + } + $lookup_types[$row->type][] = $row->guid; + $guid_to_key[$row->guid] = $i; + } + } + // Do secondary queries and merge rows + if ($lookup_types) { + $dbprefix = elgg_get_config('dbprefix'); + + foreach ($lookup_types as $type => $guids) { + $set = "(" . implode(',', $guids) . ")"; + $sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set"; + $secondary_rows = get_data($sql); + if ($secondary_rows) { + foreach ($secondary_rows as $secondary_row) { + $key = $guid_to_key[$secondary_row->guid]; + // cast to arrays to merge then cast back + $rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row); + } + } } } - - if ($owner_guid) { - if (is_array($owner_guid)) { - $options['owner_guids'] = $owner_guid; + // Second pass to finish conversion + foreach ($rows as $i => $row) { + if ($row instanceof ElggEntity) { + continue; } else { - $options['owner_guid'] = $owner_guid; + try { + $rows[$i] = entity_row_to_elggstar($row); + } catch (IncompleteEntityException $e) { + // don't let incomplete entities throw fatal errors + unset($rows[$i]); + + // report incompletes to the batch process that spawned this query + if ($batch) { + $batch->reportIncompleteEntity($row); + } + } } } - - if ($order_by) { - $options['order_by'] = $order_by; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } - - if ($count) { - $options['count'] = $count; - } - - if ($site_guid) { - $options['site_guids'] = $site_guid; - } - - if ($container_guid) { - $options['container_guids'] = $container_guid; - } - - if ($timeupper) { - $options['time_upper'] = $timeupper; - } - - if ($timelower) { - $options['time_lower'] = $timelower; - } - - $r = elgg_get_entities($options); - return $r; + return $rows; } /** - * Returns type and subtype SQL appropriate for inclusion in an IN clause. + * Returns SQL where clause for type and subtype on main entity table + * + * @param string $table Entity table prefix as defined in SELECT...FROM entities $table + * @param NULL|array $types Array of types or NULL if none. + * @param NULL|array $subtypes Array of subtypes or NULL if none + * @param NULL|array $pairs Array of pairs of types and subtypes * - * @param string $table entity table prefix. - * @param NULL|$types - * @param NULL|array $subtypes - * @param NULL|array $pairs * @return FALSE|string + * @since 1.7.0 + * @access private */ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pairs) { // subtype depends upon type. @@ -1885,8 +1206,8 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair return ''; } - // these are the only valid types for entities in elgg as defined in the DB. - $valid_types = array('object', 'user', 'group', 'site'); + // these are the only valid types for entities in elgg + $valid_types = elgg_get_config('entity_types'); // pairs override $wheres = array(); @@ -1899,69 +1220,122 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair $subtypes = array($subtypes); } - // subtypes are based upon types, so we need to look at each - // type individually to get the right subtype id. + // decrementer for valid types. Return FALSE if no valid types + $valid_types_count = count($types); + $valid_subtypes_count = 0; + // remove invalid types to get an accurate count of + // valid types for the invalid subtype detection to use + // below. + // also grab the count of ALL subtypes on valid types to decrement later on + // and check against. + // + // yes this is duplicating a foreach on $types. foreach ($types as $type) { if (!in_array($type, $valid_types)) { - return FALSE; + $valid_types_count--; + unset($types[array_search($type, $types)]); + } else { + // do the checking (and decrementing) in the subtype section. + $valid_subtypes_count += count($subtypes); } + } + // return false if nothing is valid. + if (!$valid_types_count) { + return FALSE; + } + + // subtypes are based upon types, so we need to look at each + // type individually to get the right subtype id. + foreach ($types as $type) { $subtype_ids = array(); if ($subtypes) { - // if there is only one subtype and it is is not 0 and it invalid return false. - // if the type is 0 or null, let it through. - // if the type is set but the subtype is FALSE, return false. - if (count($subtypes) === 1) { - if ($subtypes[0] && !get_subtype_id($type, $subtypes[0])) { - return FALSE; - } - } - - // subtypes can be NULL or '' or 0, which means "no subtype" foreach ($subtypes as $subtype) { - // if a subtype is sent that doesn't exist - if (0 === $subtype || $subtype_id = get_subtype_id($type, $subtype)) { - $subtype_ids[] = (0 === $subtype) ? 0 : $subtype_id; - } else { - // @todo should return false. - //return FALSE; - - elgg_log("Type-subtype $type:$subtype' does not exist!", 'WARNING'); + // check that the subtype is valid + if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) { + // subtype value is 0 + $subtype_ids[] = ELGG_ENTITIES_NO_VALUE; + } elseif (!$subtype) { + // subtype is ignored. + // this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0 continue; + } else { + $subtype_id = get_subtype_id($type, $subtype); + + if ($subtype_id) { + $subtype_ids[] = $subtype_id; + } else { + $valid_subtypes_count--; + elgg_log("Type-subtype '$type:$subtype' does not exist!", 'NOTICE'); + continue; + } } } + + // return false if we're all invalid subtypes in the only valid type + if ($valid_subtypes_count <= 0) { + return FALSE; + } } - } - //if ($subtype_ids_str = implode(',', $subtype_ids)) { - if (is_array($subtype_ids) && count($subtype_ids)) { - $subtype_ids_str = implode(',', $subtype_ids); - $wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))"; - } else { - $wheres[] = "({$table}.type = '$type')"; - } + if (is_array($subtype_ids) && count($subtype_ids)) { + $subtype_ids_str = implode(',', $subtype_ids); + $wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))"; + } else { + $wheres[] = "({$table}.type = '$type')"; + } + } } else { // using type/subtype pairs + $valid_pairs_count = count($pairs); + $valid_pairs_subtypes_count = 0; + + // same deal as above--we need to know how many valid types + // and subtypes we have before hitting the subtype section. + // also normalize the subtypes into arrays here. foreach ($pairs as $paired_type => $paired_subtypes) { - if ($paired_subtypes && !is_array($paired_subtypes)) { - $paired_subtypes = array($paired_subtypes); + if (!in_array($paired_type, $valid_types)) { + $valid_pairs_count--; + unset($pairs[array_search($paired_type, $pairs)]); + } else { + if ($paired_subtypes && !is_array($paired_subtypes)) { + $pairs[$paired_type] = array($paired_subtypes); + } + $valid_pairs_subtypes_count += count($paired_subtypes); } + } - if (is_array($paired_subtypes) && count($paired_subtypes)) { + if ($valid_pairs_count <= 0) { + return FALSE; + } + foreach ($pairs as $paired_type => $paired_subtypes) { + // this will always be an array because of line 2027, right? + // no...some overly clever person can say pair => array('object' => null) + if (is_array($paired_subtypes)) { $paired_subtype_ids = array(); foreach ($paired_subtypes as $paired_subtype) { - if ($paired_subtype && ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) { - $paired_subtype_ids[] = $paired_subtype_id; + if (ELGG_ENTITIES_NO_VALUE === $paired_subtype + || ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) { + + $paired_subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $paired_subtype) ? + ELGG_ENTITIES_NO_VALUE : $paired_subtype_id; } else { - // @todo should return false. - //return FALSE; - elgg_log("Paired type-subtype $paired_type:$paired_subtype' does not exist!", 'WARNING'); + $valid_pairs_subtypes_count--; + elgg_log("Type-subtype '$paired_type:$paired_subtype' does not exist!", 'NOTICE'); + // return false if we're all invalid subtypes in the only valid type continue; } } + // return false if there are no valid subtypes. + if ($valid_pairs_subtypes_count <= 0) { + return FALSE; + } + + if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) { - $wheres[] = "({$table}.type = '$paired_type' AND {$table}.subtype IN ($paired_subtype_ids_str))"; + $wheres[] = "({$table}.type = '$paired_type'" + . " AND {$table}.subtype IN ($paired_subtype_ids_str))"; } } else { $wheres[] = "({$table}.type = '$paired_type')"; @@ -1978,77 +1352,47 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair return ''; } - /** - * Returns SQL for owner and containers. + * Returns SQL where clause for owner and containers. * - * @todo Probably DRY up once things are settled. - * @param str $table - * @param NULL|array $owner_guids - * @return FALSE|str + * @param string $column Column name the guids should be checked against. Usually + * best to provide in table.column format. + * @param NULL|array $guids Array of GUIDs. + * + * @return false|string + * @since 1.8.0 + * @access private */ -function elgg_get_entity_owner_where_sql($table, $owner_guids) { +function elgg_get_guid_based_where_sql($column, $guids) { // short circuit if nothing requested - // 0 is a valid owner_guid. - if (!$owner_guids && $owner_guids !== 0) { + // 0 is a valid guid + if (!$guids && $guids !== 0) { return ''; } // normalize and sanitise owners - if (!is_array($owner_guids)) { - $owner_guids = array($owner_guids); - } - - $owner_guids_sanitised = array(); - foreach ($owner_guids as $owner_guid) { - if (($owner_guid != sanitise_int($owner_guid))) { - return FALSE; - } - $owner_guids_sanitised[] = $owner_guid; + if (!is_array($guids)) { + $guids = array($guids); } - $where = ''; + $guids_sanitized = array(); + foreach ($guids as $guid) { + if ($guid !== ELGG_ENTITIES_NO_VALUE) { + $guid = sanitise_int($guid); - // implode(',', 0) returns 0. - if (($owner_str = implode(',', $owner_guids_sanitised)) && ($owner_str !== FALSE) && ($owner_str !== '')) { - $where = "({$table}.owner_guid IN ($owner_str))"; - } - - return $where; -} - -/** - * Returns SQL for containers. - * - * @param string $table entity table prefix - * @param NULL|array $container_guids - * @return FALSE|string - */ -function elgg_get_entity_container_where_sql($table, $container_guids) { - // short circuit if nothing is requested. - // 0 is a valid container_guid. - if (!$container_guids && $container_guids !== 0) { - return ''; - } - - // normalize and sanitise containers - if (!is_array($container_guids)) { - $container_guids = array($container_guids); - } - - $container_guids_sanitised = array(); - foreach ($container_guids as $container_guid) { - if (($container_guid != sanitise_int($container_guid))) { - return FALSE; + if (!$guid) { + return false; + } } - $container_guids_sanitised[] = $container_guid; + $guids_sanitized[] = $guid; } $where = ''; + $guid_str = implode(',', $guids_sanitized); // implode(',', 0) returns 0. - if (FALSE !== $container_str = implode(',', $container_guids_sanitised)) { - $where = "({$table}.container_guid IN ($container_str))"; + if ($guid_str !== FALSE && $guid_str !== '') { + $where = "($column IN ($guid_str))"; } return $where; @@ -2057,16 +1401,19 @@ function elgg_get_entity_container_where_sql($table, $container_guids) { /** * Returns SQL where clause for entity time limits. * - * @param string $table Prefix for entity table name. - * @param NULL|int $time_created_upper - * @param NULL|int $time_created_lower - * @param NULL|int $time_updated_upper - * @param NULL|int $time_updated_lower + * @param string $table Entity table prefix as defined in + * SELECT...FROM entities $table + * @param NULL|int $time_created_upper Time created upper limit + * @param NULL|int $time_created_lower Time created lower limit + * @param NULL|int $time_updated_upper Time updated upper limit + * @param NULL|int $time_updated_lower Time updated lower limit * - * @return FALSE|str FALSE on fail, string on success. + * @return FALSE|string FALSE on fail, string on success. + * @since 1.7.0 + * @access private */ -function elgg_get_entity_time_where_sql($table, $time_created_upper = NULL, $time_created_lower = NULL, - $time_updated_upper = NULL, $time_updated_lower = NULL) { +function elgg_get_entity_time_where_sql($table, $time_created_upper = NULL, +$time_created_lower = NULL, $time_updated_upper = NULL, $time_updated_lower = NULL) { $wheres = array(); @@ -2096,141 +1443,84 @@ function elgg_get_entity_time_where_sql($table, $time_created_upper = NULL, $tim } /** - * Gets SQL for site entities + * Returns a string of parsed entities. * - * @param string $table entity table name - * @param NULL|array $site_guids - * @return FALSE|string - */ -function elgg_get_entity_site_where_sql($table, $site_guids) { - // short circuit if nothing requested - if (!$site_guids) { - return ''; - } - - if (!is_array($site_guids)) { - $site_guids = array($site_guids); - } - - $site_guids_sanitised = array(); - foreach ($site_guids as $site_guid) { - if (!$site_guid || ($site_guid != sanitise_int($site_guids))) { - return FALSE; - } - $site_guids_sanitised[] = $site_guid; - } - - if ($site_guids_str = implode(',', $site_guids_sanitised)) { - return "({$table}.site_guid IN ($site_guids_str))"; - } - - return ''; -} - -/** - * Returns a viewable list of entities - * - * @see elgg_view_entity_list + * Displays list of entities with formatting specified + * by the entity view. * - * @param array $options Any elgg_get_entity() options plus: + * @tip Pagination is handled automatically. * - * full_view => BOOL Display full view entities + * @internal This also provides the views for elgg_view_annotation(). * - * view_type_toggle => BOOL Display gallery / list switch + * @param array $options Any options from $getter options plus: + * full_view => BOOL Display full view entities + * list_type => STR 'list' or 'gallery' + * list_type_toggle => BOOL Display gallery / list switch + * pagination => BOOL Display pagination links * - * pagination => BOOL Display pagination links + * @param mixed $getter The entity getter function to use to fetch the entities + * @param mixed $viewer The function to use to view the entity list. * - * @return str + * @return string + * @since 1.7 + * @see elgg_get_entities() + * @see elgg_view_entity_list() + * @link http://docs.elgg.org/Entities/Output */ -function elgg_list_entities($options) { - $defaults = array( - 'offset' => 0, - 'limit' => 10, - 'full_view' => TRUE, - 'view_type_toggle' => FALSE, - 'pagination' => TRUE - ); - $options = array_merge($defaults, $options); - - $count = elgg_get_entities(array_merge(array('count' => TRUE), $options)); - $entities = elgg_get_entities($options); +function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entities', + $viewer = 'elgg_view_entity_list') { - return elgg_view_entity_list($entities, $count, $options['offset'], - $options['limit'], $options['full_view'], $options['view_type_toggle'], $options['pagination']); -} - -/** - * @deprecated 1.7. Use elgg_list_entities(). - * @param $type - * @param $subtype - * @param $owner_guid - * @param $limit - * @param $fullview - * @param $viewtypetoggle - * @param $pagination - * @return unknown_type - */ -function list_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false, $pagination = true) { - elgg_log('list_entities() was deprecated in 1.7. Use elgg_list_entities()!', 'WARNING'); + global $autofeed; + $autofeed = true; - $options = array(); + $offset_key = isset($options['offset_key']) ? $options['offset_key'] : 'offset'; - // rewrite owner_guid to container_guid to emulate old functionality - if ($owner_guid) { - $options['container_guids'] = $owner_guid; - } + $defaults = array( + 'offset' => (int) max(get_input($offset_key, 0), 0), + 'limit' => (int) max(get_input('limit', 10), 0), + 'full_view' => TRUE, + 'list_type_toggle' => FALSE, + 'pagination' => TRUE, + ); - if ($type) { - $options['types'] = $type; - } + $options = array_merge($defaults, $options); - if ($subtype) { - $options['subtypes'] = $subtype; + //backwards compatibility + if (isset($options['view_type_toggle'])) { + $options['list_type_toggle'] = $options['view_type_toggle']; } - if ($limit) { - $options['limit'] = $limit; - } + $options['count'] = TRUE; + $count = $getter($options); - if ($offset = sanitise_int(get_input('offset', null))) { - $options['offset'] = $offset; - } + $options['count'] = FALSE; + $entities = $getter($options); - $options['full_view'] = $fullview; - $options['view_toggle_type'] = $viewtoggletype; - $options['pagination'] = $pagination; + $options['count'] = $count; - return elgg_list_entities($options); + return $viewer($entities, $options); } /** - * Returns a viewable list of entities contained in a number of groups. + * Returns a list of months in which entities were updated or created. * - * @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); -} - -/** - * Returns a list of months containing content specified by the parameters + * @tip Use this to generate a list of archives by month for when entities were added or updated. + * + * @todo document how to pass in array for $subtype + * + * @warning Months are returned in the form YYYYMM. * - * @param string $type The type of entity - * @param string $subtype The subtype of entity - * @param int $container_guid The container GUID that the entinties belong to - * @param int $site_guid The site GUID - * @return array|false Either an array of timestamps, or false on failure + * @param string $type The type of entity + * @param string $subtype The subtype of entity + * @param int $container_guid The container GUID that the entities belong to + * @param int $site_guid The site GUID + * @param string $order_by Order_by SQL order by clause + * + * @return array|false Either an array months as YYYYMM, or false on failure */ -function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0) { +function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0, +$order_by = 'time_created') { + global $CONFIG; $site_guid = (int) $site_guid; @@ -2247,16 +1537,19 @@ function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_ if (is_array($subtype)) { $tempwhere = ""; if (sizeof($subtype)) { - foreach($subtype as $typekey => $subtypearray) { - foreach($subtypearray as $subtypeval) { + foreach ($subtype as $typekey => $subtypearray) { + foreach ($subtypearray as $subtypeval) { $typekey = sanitise_string($typekey); if (!empty($subtypeval)) { - if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) + if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) { return false; + } } else { $subtypeval = 0; } - if (!empty($tempwhere)) $tempwhere .= " or "; + if (!empty($tempwhere)) { + $tempwhere .= " or "; + } $tempwhere .= "(type = '{$typekey}' and subtype = {$subtypeval})"; } } @@ -2265,19 +1558,21 @@ function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_ $where[] = "({$tempwhere})"; } } else { - if ($subtype AND !$subtype = get_subtype_id($type, $subtype)) { - return false; - } else { - $where[] = "subtype=$subtype"; + if ($subtype) { + if (!$subtype_id = get_subtype_id($type, $subtype)) { + return FALSE; + } else { + $where[] = "subtype=$subtype_id"; + } } } if ($container_guid !== 0) { if (is_array($container_guid)) { - foreach($container_guid as $key => $val) { + foreach ($container_guid as $key => $val) { $container_guid[$key] = (int) $val; } - $where[] = "container_guid in (" . implode(",",$container_guid) . ")"; + $where[] = "container_guid in (" . implode(",", $container_guid) . ")"; } else { $container_guid = (int) $container_guid; $where[] = "container_guid = {$container_guid}"; @@ -2297,10 +1592,10 @@ function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_ $sql .= " $w and "; } - $sql .= "1=1"; + $sql .= "1=1 ORDER BY $order_by"; if ($result = get_data($sql)) { $endresult = array(); - foreach($result as $res) { + foreach ($result as $res) { $endresult[] = $res->yearmonth; } return $endresult; @@ -2309,10 +1604,26 @@ function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_ } /** - * Disable an entity but not delete it. + * Disable an entity. * - * @param int $guid The guid - * @param string $reason Optional reason + * Disabled entities do not show up in list or elgg_get_entity() + * calls, but still exist in the database. + * + * Entities are disabled by setting disabled = yes in the + * entities table. + * + * You can ignore the disabled field by using {@link access_show_hidden_entities()}. + * + * @note Use ElggEntity::disable() instead. + * + * @param int $guid The guid + * @param string $reason Optional reason + * @param bool $recursive Recursively disable all entities owned or contained by $guid? + * + * @return bool + * @see access_show_hidden_entities() + * @link http://docs.elgg.org/Entities + * @access private */ function disable_entity($guid, $reason = "", $recursive = true) { global $CONFIG; @@ -2321,35 +1632,41 @@ function disable_entity($guid, $reason = "", $recursive = true) { $reason = sanitise_string($reason); if ($entity = get_entity($guid)) { - if (trigger_elgg_event('disable',$entity->type,$entity)) { + if (elgg_trigger_event('disable', $entity->type, $entity)) { if ($entity->canEdit()) { if ($reason) { - create_metadata($guid, 'disable_reason', $reason,'', 0, ACCESS_PUBLIC); + create_metadata($guid, 'disable_reason', $reason, '', 0, ACCESS_PUBLIC); } if ($recursive) { - // Temporary token overriding access controls TODO: Do this better. - static $__RECURSIVE_DELETE_TOKEN; - // Make it slightly harder to guess - $__RECURSIVE_DELETE_TOKEN = md5(get_loggedin_userid()); - - $sub_entities = get_data("SELECT * from {$CONFIG->dbprefix}entities - WHERE container_guid=$guid - or owner_guid=$guid - or site_guid=$guid", 'entity_row_to_elggstar'); + $hidden = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $ia = elgg_set_ignore_access(true); + + $sub_entities = get_data("SELECT * FROM {$CONFIG->dbprefix}entities + WHERE ( + container_guid = $guid + OR owner_guid = $guid + OR site_guid = $guid + ) AND enabled='yes'", 'entity_row_to_elggstar'); if ($sub_entities) { foreach ($sub_entities as $e) { + add_entity_relationship($e->guid, 'disabled_with', $entity->guid); $e->disable($reason); } } - - $__RECURSIVE_DELETE_TOKEN = null; + access_show_hidden_entities($hidden); + elgg_set_ignore_access($ia); } + $entity->disableMetadata(); + $entity->disableAnnotations(); + _elgg_invalidate_cache_for_entity($guid); + $res = update_data("UPDATE {$CONFIG->dbprefix}entities - set enabled='no' - where guid={$guid}"); + SET enabled = 'no' + WHERE guid = $guid"); return $res; } @@ -2359,81 +1676,155 @@ function disable_entity($guid, $reason = "", $recursive = true) { } /** - * Enable an entity again. + * Enable an entity. + * + * @warning In order to enable an entity, you must first use + * {@link access_show_hidden_entities()}. * - * @param int $guid + * @param int $guid GUID of entity to enable + * @param bool $recursive Recursively enable all entities disabled with the entity? + * + * @return bool */ -function enable_entity($guid) { +function enable_entity($guid, $recursive = true) { global $CONFIG; $guid = (int)$guid; // Override access only visible entities - $access_status = access_get_show_hidden_status(); + $old_access_status = access_get_show_hidden_status(); access_show_hidden_entities(true); + $result = false; if ($entity = get_entity($guid)) { - if (trigger_elgg_event('enable',$entity->type,$entity)) { + if (elgg_trigger_event('enable', $entity->type, $entity)) { if ($entity->canEdit()) { - access_show_hidden_entities($access_status); - $result = update_data("UPDATE {$CONFIG->dbprefix}entities - set enabled='yes' - where guid={$guid}"); - $entity->clearMetaData('disable_reason'); + SET enabled = 'yes' + WHERE guid = $guid"); + + $entity->deleteMetadata('disable_reason'); + $entity->enableMetadata(); + $entity->enableAnnotations(); - return $result; + if ($recursive) { + $disabled_with_it = elgg_get_entities_from_relationship(array( + 'relationship' => 'disabled_with', + 'relationship_guid' => $entity->guid, + 'inverse_relationship' => true, + 'limit' => 0, + )); + + foreach ($disabled_with_it as $e) { + $e->enable(); + remove_entity_relationship($e->guid, 'disabled_with', $entity->guid); + } + } } } } - access_show_hidden_entities($access_status); - return false; + access_show_hidden_entities($old_access_status); + return $result; } /** - * Delete a given entity. + * Delete an entity. + * + * Removes an entity and its metadata, annotations, relationships, river entries, + * and private data. + * + * Optionally can remove entities contained and owned by $guid. * - * @param int $guid - * @param bool $recursive If true (default) then all entities which are owned or contained by $guid will also be deleted. - * Note: this bypasses ownership of sub items. + * @tip Use ElggEntity::delete() instead. + * + * @warning If deleting recursively, this bypasses ownership of items contained by + * the entity. That means that if the container_guid = $guid, the item will be deleted + * regardless of who owns it. + * + * @param int $guid The guid of the entity to delete + * @param bool $recursive If true (default) then all entities which are + * owned or contained by $guid will also be deleted. + * + * @return bool + * @access private */ function delete_entity($guid, $recursive = true) { - global $CONFIG; + global $CONFIG, $ENTITY_CACHE; $guid = (int)$guid; if ($entity = get_entity($guid)) { - if (trigger_elgg_event('delete', $entity->type, $entity)) { + if (elgg_trigger_event('delete', $entity->type, $entity)) { if ($entity->canEdit()) { + // delete cache + if (isset($ENTITY_CACHE[$guid])) { + _elgg_invalidate_cache_for_entity($guid); + } + + // If memcache is available then delete this entry from the cache + static $newentity_cache; + if ((!$newentity_cache) && (is_memcache_available())) { + $newentity_cache = new ElggMemcache('new_entity_cache'); + } + if ($newentity_cache) { + $newentity_cache->delete($guid); + } + // Delete contained owned and otherwise releated objects (depth first) if ($recursive) { - // Temporary token overriding access controls TODO: Do this better. + // Temporary token overriding access controls + // @todo Do this better. static $__RECURSIVE_DELETE_TOKEN; // Make it slightly harder to guess - $__RECURSIVE_DELETE_TOKEN = md5(get_loggedin_userid()); - - $sub_entities = get_data("SELECT * from {$CONFIG->dbprefix}entities - WHERE container_guid=$guid - or owner_guid=$guid - or site_guid=$guid", 'entity_row_to_elggstar'); - if ($sub_entities) { - foreach ($sub_entities as $e) { - $e->delete(); - } + $__RECURSIVE_DELETE_TOKEN = md5(elgg_get_logged_in_user_guid()); + + $entity_disable_override = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $ia = elgg_set_ignore_access(true); + + // @todo there was logic in the original code that ignored + // entities with owner or container guids of themselves. + // this should probably be prevented in ElggEntity instead of checked for here + $options = array( + 'wheres' => array( + "((container_guid = $guid OR owner_guid = $guid OR site_guid = $guid)" + . " AND guid != $guid)" + ), + 'limit' => 0 + ); + + $batch = new ElggBatch('elgg_get_entities', $options); + $batch->setIncrementOffset(false); + + foreach ($batch as $e) { + $e->delete(true); } + access_show_hidden_entities($entity_disable_override); $__RECURSIVE_DELETE_TOKEN = null; + elgg_set_ignore_access($ia); } + $entity_disable_override = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $ia = elgg_set_ignore_access(true); + // Now delete the entity itself - $entity->clearMetadata(); - $entity->clearAnnotations(); - $entity->clearRelationships(); - remove_from_river_by_subject($guid); - remove_from_river_by_object($guid); + $entity->deleteMetadata(); + $entity->deleteOwnedMetadata(); + $entity->deleteAnnotations(); + $entity->deleteOwnedAnnotations(); + $entity->deleteRelationships(); + + access_show_hidden_entities($entity_disable_override); + elgg_set_ignore_access($ia); + + elgg_delete_river(array('subject_guid' => $guid)); + elgg_delete_river(array('object_guid' => $guid)); remove_all_private_settings($guid); + $res = delete_data("DELETE from {$CONFIG->dbprefix}entities where guid={$guid}"); if ($res) { $sub_table = ""; @@ -2459,7 +1850,7 @@ function delete_entity($guid, $recursive = true) { } } - return $res; + return (bool)$res; } } } @@ -2468,28 +1859,18 @@ function delete_entity($guid, $recursive = true) { } /** - * 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. - * - * @deprecated 1.7. This is a dangerous function as it defaults to deleting everything. - * @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) { - elgg_log('delete_entities() was deprecated in 1.7 because no one should use it.'); - return false; -} - -/** - * 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 + * Exports attributes generated on the fly (volatile) about an entity. + * + * @param string $hook volatile + * @param string $entity_type metadata + * @param string $returnvalue Return value from previous hook + * @param array $params The parameters, passed 'guid' and 'varname' + * + * @return ElggMetadata|null + * @elgg_plugin_hook_handler volatile metadata + * @todo investigate more. + * @access private + * @todo document */ function volatile_data_export_plugin_hook($hook, $entity_type, $returnvalue, $params) { $guid = (int)$params['guid']; @@ -2518,7 +1899,20 @@ function volatile_data_export_plugin_hook($hook, $entity_type, $returnvalue, $pa } /** - * Handler called by trigger_plugin_hook on the "export" event. + * Exports all attributes of an entity. + * + * @warning Only exports fields in the entity and entity type tables. + * + * @param string $hook export + * @param string $entity_type all + * @param mixed $returnvalue Previous hook return value + * @param array $params Parameters + * + * @elgg_event_handler export all + * @return mixed + * @access private + * + * @throws InvalidParameterException|InvalidClassException */ function export_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { // Sanity check values @@ -2535,7 +1929,8 @@ function export_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { // Get the entity $entity = get_entity($guid); if (!($entity instanceof ElggEntity)) { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class())); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class())); + throw new InvalidClassException($msg); } $export = $entity->export(); @@ -2552,10 +1947,16 @@ function export_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { } /** - * Utility function used by import_entity_plugin_hook() to process an ODDEntity into an unsaved ElggEntity. + * 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. + * @todo Remove this. + * @access private + * + * @throws ClassException|InstallationException|ImportException */ function oddentity_to_elggentity(ODDEntity $element) { $class = $element->getAttribute('class'); @@ -2567,18 +1968,18 @@ function oddentity_to_elggentity(ODDEntity $element) { if (!$tmp) { // Construct new class with owner from session $classname = get_subtype_class($class, $subclass); - if ($classname!="") { + if ($classname) { if (class_exists($classname)) { $tmp = new $classname(); if (!($tmp instanceof ElggEntity)) { - throw new ClassException(sprintf(elgg_echo('ClassException:ClassnameNotClass', $classname, get_class()))); + $msg = elgg_echo('ClassException:ClassnameNotClass', array($classname, get_class())); + throw new ClassException($msg); } + } else { + error_log(elgg_echo('ClassNotFoundException:MissingClass', array($classname))); } - else - error_log(sprintf(elgg_echo('ClassNotFoundException:MissingClass'), $classname)); - } - else { + } else { switch ($class) { case 'object' : $tmp = new ElggObject($row); @@ -2593,14 +1994,16 @@ function oddentity_to_elggentity(ODDEntity $element) { $tmp = new ElggSite($row); break; default: - throw new InstallationException(sprintf(elgg_echo('InstallationException:TypeNotSupported'), $class)); + $msg = elgg_echo('InstallationException:TypeNotSupported', array($class)); + throw new InstallationException($msg); } } } if ($tmp) { if (!$tmp->import($element)) { - throw new ImportException(sprintf(elgg_echo('ImportException:ImportFailed'), $element->getAttribute('uuid'))); + $msg = elgg_echo('ImportException:ImportFailed', array($element->getAttribute('uuid'))); + throw new ImportException($msg); } return $tmp; @@ -2611,13 +2014,27 @@ function oddentity_to_elggentity(ODDEntity $element) { /** * 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. + * + * 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. + * + * @param string $hook import + * @param string $entity_type all + * @param mixed $returnvalue Value from previous hook + * @param mixed $params Array of params + * + * @return mixed + * @elgg_plugin_hook_handler import all + * @todo document + * @access private + * + * @throws ImportException */ function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { $element = $params['element']; - $tmp = NULL; + $tmp = null; if ($element instanceof ODDEntity) { $tmp = oddentity_to_elggentity($element); @@ -2625,7 +2042,8 @@ function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { if ($tmp) { // Make sure its saved if (!$tmp->save()) { - throw new ImportException(sprintf(elgg_echo('ImportException:ProblemSaving'), $element->getAttribute('uuid'))); + $msg = elgg_echo('ImportException:ProblemSaving', array($element->getAttribute('uuid'))); + throw new ImportException($msg); } // Belts and braces @@ -2642,32 +2060,34 @@ function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { } /** - * Determines whether or not the specified user can edit the specified entity. + * Returns if $user_guid is able to edit $entity_guid. + * + * @tip Can be overridden by by registering for the permissions_check + * plugin hook. * - * This is extendible by registering a plugin hook taking in the parameters 'entity' and 'user', - * which are the entity and user entities respectively + * @warning If a $user_guid is not passed it will default to the logged in user. * - * @see register_plugin_hook + * @tip Use ElggEntity::canEdit() instead. * * @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. + * @param int $user_guid The GUID of the user + * + * @return bool + * @link http://docs.elgg.org/Entities/AccessControl */ function can_edit_entity($entity_guid, $user_guid = 0) { - global $CONFIG; - $user_guid = (int)$user_guid; $user = get_entity($user_guid); if (!$user) { - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); } + $return = false; if ($entity = get_entity($entity_guid)) { - $return = false; // Test user if possible - should default to false unless a plugin hook says otherwise - if (!is_null($user)) { - if ($entity->getOwner() == $user->getGUID()) { + if ($user) { + if ($entity->getOwnerGUID() == $user->getGUID()) { $return = true; } if ($entity->container_guid == $user->getGUID()) { @@ -2677,125 +2097,67 @@ function can_edit_entity($entity_guid, $user_guid = 0) { $return = true; } if ($container_entity = get_entity($entity->container_guid)) { - if ($container_entity->canEdit()) { + if ($container_entity->canEdit($user->getGUID())) { $return = true; } } } + } - return trigger_plugin_hook('permissions_check', $entity->type, + return elgg_trigger_plugin_hook('permissions_check', $entity->type, array('entity' => $entity, 'user' => $user), $return); - - } else { - return false; - } } /** - * Determines whether or not the specified user can edit metadata on the specified entity. + * Returns if $user_guid can edit the metadata on $entity_guid. * - * This is extendible by registering a plugin hook taking in the parameters 'entity' and 'user', - * which are the entity and user entities respectively + * @tip Can be overridden by by registering for the permissions_check:metadata + * plugin hook. * - * @see register_plugin_hook + * @warning If a $user_guid isn't specified, the currently logged in user is used. * - * @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. + * @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 bool + * @see elgg_register_plugin_hook_handler() */ 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) { + if ($metadata && ($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); + if ($user_guid) { + $user = get_entity($user_guid); + } else { + $user = elgg_get_logged_in_user_entity(); + } + + $params = array('entity' => $entity, 'user' => $user, 'metadata' => $metadata); + $return = elgg_trigger_plugin_hook('permissions_check:metadata', $entity->type, $params, $return); return $return; } else { return false; } } - /** - * Get the icon for an entity + * Returns the URL 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': - $size = 'master'; - break; - - case 'large' : - $size = 'large'; - break; - - case 'topbar' : - $size = 'topbar'; - break; - - 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', $entity->getType(), array('entity' => $entity, 'viewtype' => $viewtype, 'size' => $size), $url); - - // Fail, so use default - if (!$url) { - $type = $entity->getType(); - $subtype = $entity->getSubtype(); - - if (!empty($subtype)) { - $overrideurl = elgg_view("icon/{$type}/{$subtype}/{$size}",array('entity' => $entity)); - if (!empty($overrideurl)) { - return $overrideurl; - } - } - - $overrideurl = elgg_view("icon/{$type}/default/{$size}",array('entity' => $entity)); - if (!empty($overrideurl)) { - return $overrideurl; - } - - $url = $CONFIG->url . "_graphics/icons/default/$size.png"; - } - - return $url; -} - -/** - * Gets the URL for an entity, given a particular GUID + * @tip Can be overridden with {@link register_entity_url_handler()}. * * @param int $entity_guid The GUID of the entity + * * @return string The URL of the entity + * @see register_entity_url_handler() */ function get_entity_url($entity_guid) { global $CONFIG; @@ -2804,27 +2166,27 @@ function get_entity_url($entity_guid) { $url = ""; if (isset($CONFIG->entity_url_handler[$entity->getType()][$entity->getSubType()])) { - $function = $CONFIG->entity_url_handler[$entity->getType()][$entity->getSubType()]; + $function = $CONFIG->entity_url_handler[$entity->getType()][$entity->getSubType()]; if (is_callable($function)) { - $url = $function($entity); + $url = call_user_func($function, $entity); } } elseif (isset($CONFIG->entity_url_handler[$entity->getType()]['all'])) { - $function = $CONFIG->entity_url_handler[$entity->getType()]['all']; + $function = $CONFIG->entity_url_handler[$entity->getType()]['all']; if (is_callable($function)) { - $url = $function($entity); + $url = call_user_func($function, $entity); } } elseif (isset($CONFIG->entity_url_handler['all']['all'])) { - $function = $CONFIG->entity_url_handler['all']['all']; + $function = $CONFIG->entity_url_handler['all']['all']; if (is_callable($function)) { - $url = $function($entity); + $url = call_user_func($function, $entity); } } if ($url == "") { - $url = $CONFIG->url . "pg/view/" . $entity_guid; + $url = "view/" . $entity_guid; } - return $url; + return elgg_normalize_url($url); } return false; @@ -2833,15 +2195,19 @@ function get_entity_url($entity_guid) { /** * 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_type The entity type * @param string $entity_subtype The entity subtype - * @return true|false Depending on success + * @param string $function_name The function to register + * + * @return bool Depending on success + * @see get_entity_url() + * @see ElggEntity::getURL() + * @since 1.8.0 */ -function register_entity_url_handler($function_name, $entity_type = "all", $entity_subtype = "all") { +function elgg_register_entity_url_handler($entity_type, $entity_subtype, $function_name) { global $CONFIG; - if (!is_callable($function_name)) { + if (!is_callable($function_name, true)) { return false; } @@ -2859,88 +2225,101 @@ function register_entity_url_handler($function_name, $entity_type = "all", $enti } /** - * 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 + * Registers an entity type and subtype as a public-facing entity that should + * be shown in search and by {@link elgg_list_registered_entities()}. + * + * @warning Entities that aren't registered here will not show up in search. + * + * @tip Add a language string item:type:subtype to make sure the items are display properly. + * + * @param string $type The type of entity (object, site, user, group) + * @param string $subtype The subtype to register (may be blank) + * + * @return bool Depending on success + * @see get_registered_entity_types() + * @link http://docs.elgg.org/Search + * @link http://docs.elgg.org/Tutorials/Search */ -function default_entity_icon_hook($hook, $entity_type, $returnvalue, $params) { +function elgg_register_entity_type($type, $subtype = null) { 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 = "views/$viewtype/graphics/icons/$type/$subtype/$size.png"; + $type = strtolower($type); + if (!in_array($type, $CONFIG->entity_types)) { + return FALSE; + } - if (!@file_exists($CONFIG->path . $url)) { - $url = "views/$viewtype/graphics/icons/$type/default/$size.png"; - } + if (!isset($CONFIG->registered_entities)) { + $CONFIG->registered_entities = array(); + } - if(!@file_exists($CONFIG->path . $url)) { - $url = "views/$viewtype/graphics/icons/default/$size.png"; - } + if (!isset($CONFIG->registered_entities[$type])) { + $CONFIG->registered_entities[$type] = array(); + } - if (@file_exists($CONFIG->path . $url)) { - return $CONFIG->url . $url; - } + if ($subtype) { + $CONFIG->registered_entities[$type][] = $subtype; } + + return TRUE; } /** - * 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. + * Unregisters an entity type and subtype as a public-facing entity. * - * @param string $type The type of entity (object, site, user, group) + * @warning With a blank subtype, it unregisters that entity type including + * all subtypes. This must be called after all subtypes have been registered. + * + * @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 + * + * @return bool Depending on success + * @see elgg_register_entity_type() */ -function register_entity_type($type, $subtype) { +function unregister_entity_type($type, $subtype) { global $CONFIG; $type = strtolower($type); - if (!in_array($type, array('object','site','group','user'))) { - return false; + if (!in_array($type, $CONFIG->entity_types)) { + return FALSE; } if (!isset($CONFIG->registered_entities)) { - $CONFIG->registered_entities = array(); + return FALSE; } if (!isset($CONFIG->registered_entities[$type])) { - $CONFIG->registered_entities[$type] = array(); + return FALSE; } if ($subtype) { - $CONFIG->registered_entities[$type][] = $subtype; + if (in_array($subtype, $CONFIG->registered_entities[$type])) { + $key = array_search($subtype, $CONFIG->registered_entities[$type]); + unset($CONFIG->registered_entities[$type][$key]); + } else { + return FALSE; + } + } else { + unset($CONFIG->registered_entities[$type]); } - return true; + 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 + * @see elgg_register_entity_type() */ -function get_registered_entity_types($type = '') { +function get_registered_entity_types($type = null) { global $CONFIG; if (!isset($CONFIG->registered_entities)) { return false; } - if (!empty($type)) { + if ($type) { $type = strtolower($type); } if (!empty($type) && empty($CONFIG->registered_entities[$type])) { @@ -2955,75 +2334,51 @@ function get_registered_entity_types($type = '') { } /** - * Determines whether or not the specified entity type and subtype have been registered in the system + * Returns if the entity type and subtype have been registered with {@see elgg_register_entity_type()}. * - * @param string $type The type of entity (object, site, user, group) + * @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 + * + * @return bool Depending on whether or not the type has been registered */ -function is_registered_entity_type($type, $subtype) { +function is_registered_entity_type($type, $subtype = null) { global $CONFIG; if (!isset($CONFIG->registered_entities)) { return false; } + $type = strtolower($type); - if (empty($CONFIG->registered_entities[$type])) { + + // @todo registering a subtype implicitly registers the type. + // see #2684 + if (!isset($CONFIG->registered_entities[$type])) { return false; } - if (in_array($subtype, $CONFIG->registered_entities[$type])) { - return true; - } + if ($subtype && !in_array($subtype, $CONFIG->registered_entities[$type])) { + return false; + } + return true; } /** * Page handler for generic entities view system * * @param array $page Page elements from pain page handler + * + * @return bool + * @elgg_page_handler view + * @access private */ function entities_page_handler($page) { if (isset($page[0])) { global $CONFIG; - set_input('guid',$page[0]); - include($CONFIG->path . "entities/index.php"); - } -} - -/** - * @deprecated 1.7. Use elgg_list_registered_entities(). - * @param $owner_guid - * @param $limit - * @param $fullview - * @param $viewtypetoggle - * @param $allowedtypes - * @return unknown_type - */ -function list_registered_entities($owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false, $allowedtypes = true) { - elgg_log('list_registered_entities() was deprecated in 1.7 by elgg_list_registered_entities().', 'WARNING'); - - $options = array(); - - // don't want to send anything if not being used. - if ($owner_guid) { - $options['owner_guid'] = $owner_guid; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($allowedtypes) { - $options['allowed_types'] = $allowedtypes; + set_input('guid', $page[0]); + include($CONFIG->path . "pages/entities/index.php"); + return true; } - - // need to send because might be BOOL - $options['full_view'] = $fullview; - $options['view_type_toggle'] = $viewtypetoggle; - - $options['offset'] = get_input('offset', 0); - - return elgg_list_registered_entities($options); + return false; } /** @@ -3035,474 +2390,170 @@ function list_registered_entities($owner_guid = 0, $limit = 10, $fullview = true * * full_view => BOOL Display full view entities * - * view_type_toggle => BOOL Display gallery / list switch + * list_type_toggle => BOOL Display gallery / list switch * * allowed_types => TRUE|ARRAY True to show all types or an array of valid types. * * pagination => BOOL Display pagination links * * @return string A viewable list of entities + * @since 1.7.0 */ -function elgg_list_registered_entities($options) { +function elgg_list_registered_entities(array $options = array()) { + global $autofeed; + $autofeed = true; + $defaults = array( 'full_view' => TRUE, 'allowed_types' => TRUE, - 'view_type_toggle' => FALSE, + 'list_type_toggle' => FALSE, 'pagination' => TRUE, - 'offset' => 0 + 'offset' => 0, + 'types' => array(), + 'type_subtype_pairs' => array() ); $options = array_merge($defaults, $options); - $typearray = array(); - - if ($object_types = get_registered_entity_types()) { - foreach($object_types as $object_type => $subtype_array) { - if (in_array($object_type, $options['allowed_types']) || $options['allowed_types'] === TRUE) { - $typearray[$object_type] = array(); - if (is_array($subtype_array) && count($subtype_array)) { - foreach ($subtype_array as $subtype) { - $typearray[$object_type][] = $subtype; - } - } - } - } + //backwards compatibility + if (isset($options['view_type_toggle'])) { + $options['list_type_toggle'] = $options['view_type_toggle']; } - $options['type_subtype_pairs'] = $typearray; - - $count = elgg_get_entities(array_merge(array('count' => TRUE), $options)); - $entities = elgg_get_entities($options); + $types = get_registered_entity_types(); - return elgg_view_entity_list($entities, $count, $options['offset'], - $options['limit'], $options['full_view'], $options['view_type_toggle'], $options['pagination']); -} - -/** - * Get entities based on their private data, in a similar way to metadata. - * - * @param string $name The name of the setting - * @param string $value The value of the setting - * @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_from_private_setting($name = "", $value = "", $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; - } - - $name = sanitise_string($name); - $value = sanitise_string($value); - - if ($order_by == "") { - $order_by = "e.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)) { - if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) { - return false; - } - } else { - $subtypeval = 0; - } - if (!empty($tempwhere)) $tempwhere .= " or "; - $tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; + foreach ($types as $type => $subtype_array) { + if (in_array($type, $options['allowed_types']) || $options['allowed_types'] === TRUE) { + // you must explicitly register types to show up in here and in search for objects + if ($type == 'object') { + if (is_array($subtype_array) && count($subtype_array)) { + $options['type_subtype_pairs'][$type] = $subtype_array; } - } - } - if (!empty($tempwhere)) { - $where[] = "({$tempwhere})"; - } - } else { - $type = sanitise_string($type); - if ($subtype AND !$subtype = get_subtype_id($type, $subtype)) { - return false; - } - - if ($type != "") { - $where[] = "e.type='$type'"; - } - if ($subtype!=="") { - $where[] = "e.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[] = "e.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[] = "e.container_guid in (" . implode(",",$container_guid) . ")"; - } else { - $container_guid = (int) $container_guid; - $where[] = "e.container_guid = {$container_guid}"; - } - } - - if ($name!="") { - $where[] = "s.name = '$name'"; - } - - if ($value!="") { - $where[] = "s.value='$value'"; - } - - if (!$count) { - $query = "SELECT distinct e.* - from {$CONFIG->dbprefix}entities e - JOIN {$CONFIG->dbprefix}private_settings s ON e.guid=s.entity_guid where "; - } else { - $query = "SELECT count(distinct e.guid) as total - from {$CONFIG->dbprefix}entities e JOIN {$CONFIG->dbprefix}private_settings s - ON e.guid=s.entity_guid where "; - } - foreach ($where as $w) { - $query .= " $w and "; - } - // Add access controls - $query .= get_access_sql_suffix('e'); - if (!$count) { - $query .= " order by $order_by"; - if ($limit) { - // Add order and limit - $query .= " limit $offset, $limit"; - } - - $dt = get_data($query, "entity_row_to_elggstar"); - return $dt; - } else { - $total = get_data_row($query); - return $total->total; - } -} - -/** - * Get entities based on their private data by multiple keys, in a similar way to metadata. - * - * @param string $name The name of the setting - * @param string $value The value of the setting - * @param string|array $type The type of entity (eg "user", "object" etc) or array(type1 => array('subtype1', ...'subtypeN'), ...) - * @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_from_private_setting_multi(array $name, $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 = "e.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)) { - if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) { - return false; - } - } else { - $subtypeval = 0; - } - if (!empty($tempwhere)) $tempwhere .= " or "; - $tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; + } else { + if (is_array($subtype_array) && count($subtype_array)) { + $options['type_subtype_pairs'][$type] = $subtype_array; + } else { + $options['type_subtype_pairs'][$type] = ELGG_ENTITIES_ANY_VALUE; } } } - if (!empty($tempwhere)) { - $where[] = "({$tempwhere})"; - } - - } else { - $type = sanitise_string($type); - if ($subtype AND !$subtype = get_subtype_id($type, $subtype)) { - return false; - } - - if ($type != "") { - $where[] = "e.type='$type'"; - } - - if ($subtype!=="") { - $where[] = "e.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[] = "e.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[] = "e.container_guid in (" . implode(",",$container_guid) . ")"; - } else { - $container_guid = (int) $container_guid; - $where[] = "e.container_guid = {$container_guid}"; - } - } - - if ($name) { - $s_join = ""; - $i = 1; - foreach ($name as $k => $n) { - $k = sanitise_string($k); - $s_join .= " JOIN {$CONFIG->dbprefix}private_settings s$i ON e.guid=s$i.entity_guid"; - $where[] = "s$i.name = '$k'"; - $where[] = "s$i.value = '$n'"; - $i++; - } - } - - if (!$count) { - $query = "SELECT distinct e.* from {$CONFIG->dbprefix}entities e $s_join where "; + if (!empty($options['type_subtype_pairs'])) { + $count = elgg_get_entities(array_merge(array('count' => TRUE), $options)); + $entities = elgg_get_entities($options); } else { - $query = "SELECT count(distinct e.guid) as total - from {$CONFIG->dbprefix}entities e $s_join where "; - } - - foreach ($where as $w) { - $query .= " $w and "; + $count = 0; + $entities = array(); } - // Add access controls - $query .= get_access_sql_suffix('e'); - - if (!$count) { - $query .= " order by $order_by"; - // Add order and limit - if ($limit) { - $query .= " limit $offset, $limit"; - } - - $dt = get_data($query, "entity_row_to_elggstar"); - return $dt; - } else { - $total = get_data_row($query); - return $total->total; - } + $options['count'] = $count; + return elgg_view_entity_list($entities, $options); } /** - * Gets a private setting for an entity. + * Checks if $entity is an ElggEntity and optionally for type and subtype. * - * @param int $entity_guid The entity GUID - * @param string $name The name of the setting - * @return mixed The setting value, or false on failure - */ -function get_private_setting($entity_guid, $name) { - global $CONFIG; - $entity_guid = (int) $entity_guid; - $name = sanitise_string($name); - - if ($setting = get_data_row("SELECT value from {$CONFIG->dbprefix}private_settings where name = '{$name}' and entity_guid = {$entity_guid}")) { - return $setting->value; - } - return false; -} - -/** - * Return an array of all private settings for a given + * @tip Use this function in actions and views to check that you are dealing + * with the correct type of entity. * - * @param int $entity_guid The entity GUID - */ -function get_all_private_settings($entity_guid) { - global $CONFIG; - - $entity_guid = (int) $entity_guid; - - $result = get_data("SELECT * from {$CONFIG->dbprefix}private_settings where entity_guid = {$entity_guid}"); - if ($result) { - $return = array(); - foreach ($result as $r) { - $return[$r->name] = $r->value; - } - - return $return; - } - - return false; -} - -/** - * Sets a private setting for an entity. + * @param mixed $entity Entity + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param string $class Class name * - * @param int $entity_guid The entity GUID - * @param string $name The name of the setting - * @param string $value The value of the setting - * @return mixed The setting ID, or false on failure + * @return bool + * @since 1.8.0 */ -function set_private_setting($entity_guid, $name, $value) { - global $CONFIG; - - $entity_guid = (int) $entity_guid; - $name = sanitise_string($name); - $value = sanitise_string($value); +function elgg_instanceof($entity, $type = NULL, $subtype = NULL, $class = NULL) { + $return = ($entity instanceof ElggEntity); - $result = insert_data("INSERT into {$CONFIG->dbprefix}private_settings - (entity_guid, name, value) VALUES - ($entity_guid, '{$name}', '{$value}') - ON DUPLICATE KEY UPDATE value='$value'"); - if ($result === 0) { - return true; + if ($type) { + /* @var ElggEntity $entity */ + $return = $return && ($entity->getType() == $type); } - return $result; -} -/** - * Deletes a private setting for an entity. - * - * @param int $entity_guid The Entity GUID - * @param string $name The name of the setting - * @return true|false depending on success - * - */ -function remove_private_setting($entity_guid, $name) { - global $CONFIG; + if ($subtype) { + $return = $return && ($entity->getSubtype() == $subtype); + } - $entity_guid = (int) $entity_guid; - $name = sanitise_string($name); + if ($class) { + $return = $return && ($entity instanceof $class); + } - return delete_data("DELETE from {$CONFIG->dbprefix}private_settings - where name = '{$name}' - and entity_guid = {$entity_guid}"); + return $return; } /** - * Deletes all private settings for an entity. + * Update the last_action column in the entities table for $guid. * - * @param int $entity_guid The Entity GUID - * @return true|false depending on success + * @warning This is different to time_updated. Time_updated is automatically set, + * while last_action is only set when explicitly called. * - */ -function remove_all_private_settings($entity_guid) { - global $CONFIG; - - $entity_guid = (int) $entity_guid; - return delete_data("DELETE from {$CONFIG->dbprefix}private_settings - where entity_guid = {$entity_guid}"); -} - -/* - * Check the recurisve delete permissions token. + * @param int $guid Entity annotation|relationship action carried out on + * @param int $posted Timestamp of last action * * @return bool + * @access private */ -function recursive_delete_permissions_check($hook, $entity_type, $returnvalue, $params) { - static $__RECURSIVE_DELETE_TOKEN; - - $entity = $params['entity']; +function update_entity_last_action($guid, $posted = NULL) { + global $CONFIG; + $guid = (int)$guid; + $posted = (int)$posted; - if ((isloggedin()) && ($__RECURSIVE_DELETE_TOKEN) && (strcmp($__RECURSIVE_DELETE_TOKEN, md5(get_loggedin_userid())))) { - return true; + if (!$posted) { + $posted = time(); } - // consult next function - return NULL; + if ($guid) { + //now add to the river updated table + $query = "UPDATE {$CONFIG->dbprefix}entities SET last_action = {$posted} WHERE guid = {$guid}"; + $result = update_data($query); + if ($result) { + return TRUE; + } else { + return FALSE; + } + } else { + return FALSE; + } } /** * Garbage collect stub and fragments from any broken delete/create calls * - * @param unknown_type $hook - * @param unknown_type $user - * @param unknown_type $returnvalue - * @param unknown_type $tag + * @return void + * @elgg_plugin_hook_handler gc system + * @access private */ -function entities_gc($hook, $user, $returnvalue, $tag) { +function entities_gc() { global $CONFIG; - $tables = array ('sites_entity', 'objects_entity', 'groups_entity', 'users_entity'); + $tables = array( + 'site' => 'sites_entity', + 'object' => 'objects_entity', + 'group' => 'groups_entity', + 'user' => 'users_entity' + ); - foreach ($tables as $table) { - delete_data("DELETE from {$CONFIG->dbprefix}{$table} - where guid NOT IN (SELECT guid from {$CONFIG->dbprefix}entities)"); + foreach ($tables as $type => $table) { + delete_data("DELETE FROM {$CONFIG->dbprefix}{$table} + WHERE guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}entities)"); + delete_data("DELETE FROM {$CONFIG->dbprefix}entities + WHERE type = '$type' AND guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}{$table})"); } } /** - * Runs unit tests for the entities object. + * Runs unit tests for the entity objects. + * + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params + * + * @return array + * @access private */ function entities_test($hook, $type, $value, $params) { global $CONFIG; @@ -3511,35 +2562,29 @@ function entities_test($hook, $type, $value, $params) { } /** - * Entities init function; establishes the page handler + * Entities init function; establishes the default entity page handler * + * @return void + * @elgg_event_handler init system + * @access private */ function entities_init() { - register_page_handler('view','entities_page_handler'); + elgg_register_page_handler('view', 'entities_page_handler'); - register_plugin_hook('unit_test', 'system', 'entities_test'); + elgg_register_plugin_hook_handler('unit_test', 'system', 'entities_test'); - // Allow a permission override for recursive entity deletion - // TODO: Can this be done better? - register_plugin_hook('permissions_check','all','recursive_delete_permissions_check'); - register_plugin_hook('permissions_check:metadata','all','recursive_delete_permissions_check'); - - register_plugin_hook('gc','system','entities_gc'); - - register_plugin_hook('search','all','search_list_entities_by_name'); + elgg_register_plugin_hook_handler('gc', 'system', 'entities_gc'); } /** Register the import hook */ -register_plugin_hook("import", "all", "import_entity_plugin_hook", 0); +elgg_register_plugin_hook_handler("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); +elgg_register_plugin_hook_handler("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_icon_hook', 1000); +elgg_register_plugin_hook_handler('volatile', 'metadata', 'volatile_data_export_plugin_hook'); /** Register init system event **/ -register_elgg_event_handler('init','system','entities_init');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'entities_init'); + |
