diff options
Diffstat (limited to 'engine/lib')
102 files changed, 25720 insertions, 18491 deletions
diff --git a/engine/lib/access.php b/engine/lib/access.php index a626bae7e..de0693ea8 100644 --- a/engine/lib/access.php +++ b/engine/lib/access.php @@ -1,68 +1,62 @@ <?php /** - * Elgg access permissions - * For users, objects, collections and all metadata + * Functions for Elgg's access system for entities, metadata, and annotations. * - * @package Elgg - * @subpackage Core - - * @author Curverider Ltd - - * @link http://elgg.org/ + * Access is generally saved in the database as access_id. This corresponds to + * one of the ACCESS_* constants defined in {@link elgglib.php} or the ID of an + * access collection. + * + * @package Elgg.Core + * @subpackage Access + * @link http://docs.elgg.org/Access */ /** - * Temporary class used to determing if access is being ignored + * Return an ElggCache static variable cache for the access caches + * + * @staticvar ElggStaticVariableCache $access_cache + * @return \ElggStaticVariableCache + * @access private */ -class ElggAccess { +function _elgg_get_access_cache() { /** - * Bypass Elgg's access control if true. - * @var bool + * A default filestore cache using the dataroot. */ - private $ignore_access; + static $access_cache; - /** - * Get current ignore access setting. - * @return bool - */ - public function get_ignore_access() { - return $this->ignore_access; + if (!$access_cache) { + $access_cache = new ElggStaticVariableCache('access'); } - /** - * Set ignore access. - * - * @param $ignore bool true || false to ignore - * @return bool Previous setting - */ - public function set_ignore_access($ignore = true) { - $prev = $this->ignore_access; - $this->ignore_access = $ignore; - - return $prev; - } + return $access_cache; } - /** * Return a string of access_ids for $user_id appropriate for inserting into an SQL IN clause. * * @uses get_access_array - * @param int $user_id User ID; defaults to currently logged in user - * @param int $site_id Site ID; defaults to current site - * @param boolean $flush If set to true, will refresh the access list from the database - * @return string A list of access collections suitable for injection in an SQL call + * + * @link http://docs.elgg.org/Access + * @see get_access_array() + * + * @param int $user_id User ID; defaults to currently logged in user + * @param int $site_id Site ID; defaults to current site + * @param bool $flush If set to true, will refresh the access list from the + * database rather than using this function's cache. + * + * @return string A list of access collections suitable for using in an SQL call + * @access private */ function get_access_list($user_id = 0, $site_id = 0, $flush = false) { - global $CONFIG, $init_finished, $SESSION; - static $access_list; - - if (!isset($access_list) || !$init_finished) { - $access_list = array(); + global $CONFIG, $init_finished; + $cache = _elgg_get_access_cache(); + + if ($flush) { + $cache->clear(); } if ($user_id == 0) { - $user_id = $SESSION['id']; + $user_id = elgg_get_logged_in_user_guid(); } if (($site_id == 0) && (isset($CONFIG->site_id))) { @@ -71,36 +65,55 @@ function get_access_list($user_id = 0, $site_id = 0, $flush = false) { $user_id = (int) $user_id; $site_id = (int) $site_id; - if (isset($access_list[$user_id])) { - return $access_list[$user_id]; - } + $hash = $user_id . $site_id . 'get_access_list'; - $access_list[$user_id] = "(" . implode(",", get_access_array($user_id, $site_id, $flush)) . ")"; + if ($cache[$hash]) { + return $cache[$hash]; + } + + $access_array = get_access_array($user_id, $site_id, $flush); + $access = "(" . implode(",", $access_array) . ")"; - return $access_list[$user_id]; + if ($init_finished) { + $cache[$hash] = $access; + } + + return $access; } /** - * Gets an array of access restrictions the given user is allowed to see on this site + * Returns an array of access IDs a user is permitted to see. + * + * Can be overridden with the 'access:collections:read', 'user' plugin hook. + * + * This returns a list of all the collection ids a user owns or belongs + * to plus public and logged in access levels. If the user is an admin, it includes + * the private access level. + * + * @internal this is only used in core for creating the SQL where clause when + * retrieving content from the database. The friends access level is handled by + * get_access_sql_suffix(). * - * @param int $user_id User ID; defaults to currently logged in user - * @param int $site_id Site ID; defaults to current site - * @param boolean $flush If set to true, will refresh the access list from the database - * @return array An array of access collections suitable for injection in an SQL call + * @see get_write_access_array() for the access levels that a user can write to. + * + * @param int $user_id User ID; defaults to currently logged in user + * @param int $site_id Site ID; defaults to current site + * @param bool $flush If set to true, will refresh the access ids from the + * database rather than using this function's cache. + * + * @return array An array of access collections ids */ function get_access_array($user_id = 0, $site_id = 0, $flush = false) { global $CONFIG, $init_finished; - // @todo everything from the db is cached. - // this cache might be redundant. - static $access_array; + $cache = _elgg_get_access_cache(); - if (!isset($access_array) || (!isset($init_finished)) || (!$init_finished)) { - $access_array = array(); + if ($flush) { + $cache->clear(); } if ($user_id == 0) { - $user_id = get_loggedin_userid(); + $user_id = elgg_get_logged_in_user_guid(); } if (($site_id == 0) && (isset($CONFIG->site_guid))) { @@ -110,34 +123,41 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) { $user_id = (int) $user_id; $site_id = (int) $site_id; - if (empty($access_array[$user_id]) || $flush == true) { - $tmp_access_array = array(ACCESS_PUBLIC); - if (isloggedin()) { - $tmp_access_array[] = ACCESS_LOGGED_IN; + $hash = $user_id . $site_id . 'get_access_array'; - // The following can only return sensible data if the user is logged in. + if ($cache[$hash]) { + $access_array = $cache[$hash]; + } else { + $access_array = array(ACCESS_PUBLIC); - // Get ACL memberships - $query = "SELECT am.access_collection_id FROM {$CONFIG->dbprefix}access_collection_membership am "; - $query .= " LEFT JOIN {$CONFIG->dbprefix}access_collections ag ON ag.id = am.access_collection_id "; - $query .= " WHERE am.user_guid = {$user_id} AND (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; + // The following can only return sensible data if the user is logged in. + if (elgg_is_logged_in()) { + $access_array[] = ACCESS_LOGGED_IN; - if ($collections = get_data($query)) { - foreach($collections as $collection) { + // Get ACL memberships + $query = "SELECT am.access_collection_id" + . " FROM {$CONFIG->dbprefix}access_collection_membership am" + . " LEFT JOIN {$CONFIG->dbprefix}access_collections ag ON ag.id = am.access_collection_id" + . " WHERE am.user_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)"; + + $collections = get_data($query); + if ($collections) { + foreach ($collections as $collection) { if (!empty($collection->access_collection_id)) { - $tmp_access_array[] = $collection->access_collection_id; + $access_array[] = (int)$collection->access_collection_id; } } } // Get ACLs owned. - $query = "SELECT ag.id FROM {$CONFIG->dbprefix}access_collections ag "; - $query .= " WHERE ag.owner_guid = {$user_id} AND (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; + $query = "SELECT ag.id FROM {$CONFIG->dbprefix}access_collections ag "; + $query .= "WHERE ag.owner_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)"; - if ($collections = get_data($query)) { - foreach($collections as $collection) { + $collections = get_data($query); + if ($collections) { + foreach ($collections as $collection) { if (!empty($collection->id)) { - $tmp_access_array[] = $collection->id; + $access_array[] = (int)$collection->id; } } } @@ -145,26 +165,32 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) { $ignore_access = elgg_check_access_overrides($user_id); if ($ignore_access == true) { - $tmp_access_array[] = ACCESS_PRIVATE; + $access_array[] = ACCESS_PRIVATE; } - - $access_array[$user_id] = $tmp_access_array; - } else { - // No user id logged in so we can only access public info - $tmp_return = $tmp_access_array; } - } else { - $tmp_access_array = $access_array[$user_id]; + if ($init_finished) { + $cache[$hash] = $access_array; + } } - return trigger_plugin_hook('access:collections:read','user',array('user_id' => $user_id, 'site_id' => $site_id),$tmp_access_array); + $options = array( + 'user_id' => $user_id, + 'site_id' => $site_id + ); + + return elgg_trigger_plugin_hook('access:collections:read', 'user', $options, $access_array); } /** - * Gets the default access permission for new content + * Gets the default access permission. + * + * This returns the default access level for the site or optionally for the user. + * + * @param ElggUser $user Get the user's default access. Defaults to logged in user. * * @return int default access id (see ACCESS defines in elgglib.php) + * @link http://docs.elgg.org/Access */ function get_default_access(ElggUser $user = null) { global $CONFIG; @@ -173,7 +199,7 @@ function get_default_access(ElggUser $user = null) { return $CONFIG->default_access; } - if (!($user) || (!$user = get_loggedin_user())) { + if (!($user) && (!$user = elgg_get_logged_in_user_entity())) { return $CONFIG->default_access; } @@ -185,17 +211,20 @@ function get_default_access(ElggUser $user = null) { } /** - * Override the default behaviour and allow results to show hidden entities as well. - * THIS IS A HACK. + * Allow disabled entities and metadata to be returned by getter functions * - * TODO: Replace this with query object! + * @todo Replace this with query object! + * @global bool $ENTITY_SHOW_HIDDEN_OVERRIDE + * @access private */ $ENTITY_SHOW_HIDDEN_OVERRIDE = false; /** - * This will be replaced. Do not use in plugins! + * Show or hide disabled entities. * - * @param bool $show + * @param bool $show_hidden Show disabled entities. + * @return void + * @access private */ function access_show_hidden_entities($show_hidden) { global $ENTITY_SHOW_HIDDEN_OVERRIDE; @@ -203,7 +232,10 @@ function access_show_hidden_entities($show_hidden) { } /** - * This will be replaced. Do not use in plugins! + * Return current status of showing disabled entities. + * + * @return bool + * @access private */ function access_get_show_hidden_status() { global $ENTITY_SHOW_HIDDEN_OVERRIDE; @@ -211,45 +243,17 @@ function access_get_show_hidden_status() { } /** - * Add annotation restriction - * - * Returns an SQL fragment that is true (or optionally false) if the given user has - * added an annotation with the given name to the given entity. + * Returns the SQL where clause for a table with a access_id and enabled columns. * - * TODO: This is fairly generic so perhaps it could be moved to annotations.php - * - * @param string $annotation_name name of the annotation - * @param string $entity_guid SQL string that evaluates to the GUID of the entity the annotation should be attached to - * @param string $owner_guid SQL string that evaluates to the GUID of the owner of the annotation * - * @param boolean $exists If set to true, will return true if the annotation exists, otherwise returns false - * @return string An SQL fragment suitable for inserting into a WHERE clause - */ -function get_annotation_sql($annotation_name, $entity_guid, $owner_guid, $exists) { - global $CONFIG; - - if ($exists) { - $not = ''; - } else { - $not = 'NOT'; - } - - $sql = <<<END -$not EXISTS (SELECT * FROM {$CONFIG->dbprefix}annotations a -INNER JOIN {$CONFIG->dbprefix}metastrings ms ON (a.name_id = ms.id) -WHERE ms.string = '$annotation_name' -AND a.entity_guid = $entity_guid -AND a.owner_guid = $owner_guid) -END; - return $sql; -} - -/** - * Add access restriction sql code to a given query. - * Note that if this code is executed in privileged mode it will return blank. - * @TODO: DELETE once Query classes are fully integrated + * This handles returning where clauses for ACCESS_FRIENDS and the currently + * unused block and filter lists in addition to using get_access_list() for + * access collections and the standard access levels. * * @param string $table_prefix Optional table. prefix for the access code. - * @param int $owner + * @param int $owner The guid to check access for. Defaults to logged in user. + * + * @return string The SQL for a where clause + * @access private */ function get_access_sql_suffix($table_prefix = '', $owner = null) { global $ENTITY_SHOW_HIDDEN_OVERRIDE, $CONFIG; @@ -259,11 +263,11 @@ function get_access_sql_suffix($table_prefix = '', $owner = null) { $enemies_bit = ""; if ($table_prefix) { - $table_prefix = sanitise_string($table_prefix) . "."; + $table_prefix = sanitise_string($table_prefix) . "."; } if (!isset($owner)) { - $owner = get_loggedin_userid(); + $owner = elgg_get_logged_in_user_guid(); } if (!$owner) { @@ -276,22 +280,24 @@ function get_access_sql_suffix($table_prefix = '', $owner = null) { if ($ignore_access) { $sql = " (1 = 1) "; } else if ($owner != -1) { + // we have an entity's guid and auto check for friend relationships $friends_bit = "{$table_prefix}access_id = " . ACCESS_FRIENDS . " AND {$table_prefix}owner_guid IN ( SELECT guid_one FROM {$CONFIG->dbprefix}entity_relationships WHERE relationship='friend' AND guid_two=$owner )"; - $friends_bit = '('.$friends_bit.') OR '; + $friends_bit = '(' . $friends_bit . ') OR '; + // @todo untested and unsupported at present if ((isset($CONFIG->user_block_and_filter_enabled)) && ($CONFIG->user_block_and_filter_enabled)) { // check to see if the user is in the entity owner's block list // or if the entity owner is in the user's filter list // if so, disallow access - $enemies_bit = get_annotation_sql('elgg_block_list', "{$table_prefix}owner_guid", $owner, false); + $enemies_bit = get_access_restriction_sql('elgg_block_list', "{$table_prefix}owner_guid", $owner, false); $enemies_bit = '(' . $enemies_bit - . ' AND ' . get_annotation_sql('elgg_filter_list', $owner, "{$table_prefix}owner_guid", false) + . ' AND ' . get_access_restriction_sql('elgg_filter_list', $owner, "{$table_prefix}owner_guid", false) . ')'; } } @@ -310,18 +316,68 @@ function get_access_sql_suffix($table_prefix = '', $owner = null) { $sql = "$enemies_bit AND ($sql)"; } - if (!$ENTITY_SHOW_HIDDEN_OVERRIDE) + if (!$ENTITY_SHOW_HIDDEN_OVERRIDE) { $sql .= " and {$table_prefix}enabled='yes'"; - return '('.$sql.')'; + } + + return '(' . $sql . ')'; } /** - * Determines whether the given user has access to the given entity + * Get the where clause for an access restriction based on annotations + * + * Returns an SQL fragment that is true (or optionally false) if the given user has + * added an annotation with the given name to the given entity. + * + * @warning this is a private function for an untested capability and will likely + * be removed from a future version of Elgg. + * + * @param string $annotation_name Name of the annotation + * @param string $entity_guid SQL GUID of entity the annotation is attached to. + * @param string $owner_guid SQL string that evaluates to the GUID of the annotation owner + * @param boolean $exists If true, returns BOOL if the annotation exists + * + * @return string An SQL fragment suitable for inserting into a WHERE clause + * @access private + */ +function get_access_restriction_sql($annotation_name, $entity_guid, $owner_guid, $exists) { + global $CONFIG; + + if ($exists) { + $not = ''; + } else { + $not = 'NOT'; + } + + $sql = <<<END +$not EXISTS (SELECT * FROM {$CONFIG->dbprefix}annotations a +INNER JOIN {$CONFIG->dbprefix}metastrings ms ON (a.name_id = ms.id) +WHERE ms.string = '$annotation_name' +AND a.entity_guid = $entity_guid +AND a.owner_guid = $owner_guid) +END; + return $sql; +} + +/** + * Can a user access an entity. + * + * @warning If a logged in user doesn't have access to an entity, the + * core engine will not load that entity. + * + * @tip This is mostly useful for checking if a user other than the logged in + * user has access to an entity that is currently loaded. + * + * @todo This function would be much more useful if we could pass the guid of the + * entity to test access for. We need to be able to tell whether the entity exists + * and whether the user has access to the entity. * * @param ElggEntity $entity The entity to check access for. - * @param ElggUser $user Optionally the user to check access for. + * @param ElggUser $user Optionally user to check access for. Defaults to + * logged in user (which is a useless default). * - * @return boolean True if the user can access the entity + * @return bool + * @link http://docs.elgg.org/Access */ function has_access_to_entity($entity, $user = null) { global $CONFIG; @@ -333,7 +389,8 @@ function has_access_to_entity($entity, $user = null) { } $query = "SELECT guid from {$CONFIG->dbprefix}entities e WHERE e.guid = " . $entity->getGUID(); - $query .= " AND " . $access_bit; // Add access controls + // Add access controls + $query .= " AND " . $access_bit; if (get_data($query)) { return true; } else { @@ -342,22 +399,41 @@ function has_access_to_entity($entity, $user = null) { } /** - * Returns an array of access permissions that the specified user is allowed to save objects with. - * Permissions are of the form ('id' => 'Description') + * Returns an array of access permissions that the user is allowed to save content with. + * Permissions returned are of the form (id => 'name'). + * + * Example return value in English: + * array( + * 0 => 'Private', + * -2 => 'Friends', + * 1 => 'Logged in users', + * 2 => 'Public', + * 34 => 'My favorite friends', + * ); + * + * Plugin hook of 'access:collections:write', 'user' + * + * @warning this only returns access collections that the user owns plus the + * standard access levels. It does not return access collections that the user + * belongs to such as the access collection for a group. * - * @param int $user_id The user's GUID. - * @param int $site_id The current site. - * @param true|false $flush If this is set to true, this will shun any cached version + * @param int $user_id The user's GUID. + * @param int $site_id The current site. + * @param bool $flush If this is set to true, this will ignore a cached access array * * @return array List of access permissions + * @link http://docs.elgg.org/Access */ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) { - global $CONFIG; - //@todo this is probably not needed since caching happens at the DB level. - static $access_array; + global $CONFIG, $init_finished; + $cache = _elgg_get_access_cache(); + + if ($flush) { + $cache->clear(); + } if ($user_id == 0) { - $user_id = get_loggedin_userid(); + $user_id = elgg_get_logged_in_user_guid(); } if (($site_id == 0) && (isset($CONFIG->site_id))) { @@ -367,40 +443,99 @@ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) { $user_id = (int) $user_id; $site_id = (int) $site_id; - if (empty($access_array[$user_id]) || $flush == true) { + $hash = $user_id . $site_id . 'get_write_access_array'; + + if ($cache[$hash]) { + $access_array = $cache[$hash]; + } else { + // @todo is there such a thing as public write access? + $access_array = array( + ACCESS_PRIVATE => elgg_echo("PRIVATE"), + ACCESS_FRIENDS => elgg_echo("access:friends:label"), + ACCESS_LOGGED_IN => elgg_echo("LOGGED_IN"), + ACCESS_PUBLIC => elgg_echo("PUBLIC") + ); + $query = "SELECT ag.* FROM {$CONFIG->dbprefix}access_collections ag "; - $query .= " WHERE (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; - $query .= " AND (ag.owner_guid = {$user_id})"; - $query .= " AND ag.id >= 3"; - - $tmp_access_array = array( ACCESS_PRIVATE => elgg_echo("PRIVATE"), - ACCESS_FRIENDS => elgg_echo("access:friends:label"), - ACCESS_LOGGED_IN => elgg_echo("LOGGED_IN"), - ACCESS_PUBLIC => elgg_echo("PUBLIC")); - if ($collections = get_data($query)) { - foreach($collections as $collection) { - $tmp_access_array[$collection->id] = $collection->name; + $query .= " WHERE (ag.site_guid = $site_id OR ag.site_guid = 0)"; + $query .= " AND (ag.owner_guid = $user_id)"; + + $collections = get_data($query); + if ($collections) { + foreach ($collections as $collection) { + $access_array[$collection->id] = $collection->name; } } - $access_array[$user_id] = $tmp_access_array; + if ($init_finished) { + $cache[$hash] = $access_array; + } + } + + $options = array( + 'user_id' => $user_id, + 'site_id' => $site_id + ); + return elgg_trigger_plugin_hook('access:collections:write', 'user', + $options, $access_array); +} + +/** + * Can the user change this access collection? + * + * Use the plugin hook of 'access:collections:write', 'user' to change this. + * @see get_write_access_array() for details on the hook. + * + * Respects access control disabling for admin users and {@see elgg_set_ignore_access()} + * + * @see get_write_access_array() + * + * @param int $collection_id The collection id + * @param mixed $user_guid The user GUID to check for. Defaults to logged in user. + * @return bool + */ +function can_edit_access_collection($collection_id, $user_guid = null) { + if ($user_guid) { + $user = get_entity((int) $user_guid); } else { - $tmp_access_array = $access_array[$user_id]; + $user = elgg_get_logged_in_user_entity(); } - $tmp_access_array = trigger_plugin_hook('access:collections:write','user',array('user_id' => $user_id, 'site_id' => $site_id),$tmp_access_array); + $collection = get_access_collection($collection_id); - return $tmp_access_array; + if (!($user instanceof ElggUser) || !$collection) { + return false; + } + + $write_access = get_write_access_array($user->getGUID(), 0, true); + + // don't ignore access when checking users. + if ($user_guid) { + return array_key_exists($collection_id, $write_access); + } else { + return elgg_get_ignore_access() || array_key_exists($collection_id, $write_access); + } } /** - * Creates a new access control collection owned by the specified user. + * Creates a new access collection. + * + * Access colletions allow plugins and users to create granular access + * for entities. + * + * Triggers plugin hook 'access:collections:addcollection', 'collection' * - * @param string $name The name of the collection. - * @param int $owner_guid The GUID of the owner (default: currently logged in user). - * @param int $site_guid The GUID of the site (default: current site). + * @internal Access collections are stored in the access_collections table. + * Memberships to collections are in access_collections_membership. * - * @return int|false Depending on success (the collection ID if successful). + * @param string $name The name of the collection. + * @param int $owner_guid The GUID of the owner (default: currently logged in user). + * @param int $site_guid The GUID of the site (default: current site). + * + * @return int|false The collection ID if successful and false on failure. + * @link http://docs.elgg.org/Access/Collections + * @see update_access_collection() + * @see delete_access_collection() */ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { global $CONFIG; @@ -411,7 +546,7 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { } if ($owner_guid == 0) { - $owner_guid = get_loggedin_userid(); + $owner_guid = elgg_get_logged_in_user_guid(); } if (($site_guid == 0) && (isset($CONFIG->site_guid))) { $site_guid = $CONFIG->site_guid; @@ -422,7 +557,8 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { SET name = '{$name}', owner_guid = {$owner_guid}, site_guid = {$site_guid}"; - if (!$id = insert_data($q)) { + $id = insert_data($q); + if (!$id) { return false; } @@ -430,7 +566,7 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { 'collection_id' => $id ); - if (!trigger_plugin_hook('access:collections:addcollection', 'collection', $params, true)) { + if (!elgg_trigger_plugin_hook('access:collections:addcollection', 'collection', $params, true)) { return false; } @@ -440,195 +576,245 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { /** * Updates the membership in an access collection. * - * @param int $collection_id The ID of the collection. - * @param array $members Array of member GUIDs - * @return true|false Depending on success + * @warning Expects a full list of all members that should + * be part of the access collection + * + * @note This will run all hooks associated with adding or removing + * members to access collections. + * + * @param int $collection_id The ID of the collection. + * @param array $members Array of member GUIDs + * + * @return bool + * @link http://docs.elgg.org/Access/Collections + * @see add_user_to_access_collection() + * @see remove_user_from_access_collection() */ function update_access_collection($collection_id, $members) { - global $CONFIG; + $acl = get_access_collection($collection_id); - $collection_id = (int) $collection_id; + if (!$acl) { + return false; + } $members = (is_array($members)) ? $members : array(); - $collections = get_write_access_array(); - - if (array_key_exists($collection_id, $collections)) { - $cur_members = get_members_of_access_collection($collection_id, true); - $cur_members = (is_array($cur_members)) ? $cur_members : array(); + $cur_members = get_members_of_access_collection($collection_id, true); + $cur_members = (is_array($cur_members)) ? $cur_members : array(); - $remove_members = array_diff($cur_members, $members); - $add_members = array_diff($members, $cur_members); + $remove_members = array_diff($cur_members, $members); + $add_members = array_diff($members, $cur_members); - $params = array( - 'collection_id' => $collection_id, - 'members' => $members, - 'add_members' => $add_members, - 'remove_members' => $remove_members - ); - - foreach ($add_members as $guid) { - add_user_to_access_collection($guid, $collection_id); - } + $result = true; - foreach ($remove_members as $guid) { - remove_user_from_access_collection($guid, $collection_id); - } + foreach ($add_members as $guid) { + $result = $result && add_user_to_access_collection($guid, $collection_id); + } - return true; + foreach ($remove_members as $guid) { + $result = $result && remove_user_from_access_collection($guid, $collection_id); } - return false; + return $result; } /** - * Deletes a specified access collection + * Deletes a specified access collection and its membership. * * @param int $collection_id The collection ID - * @return true|false Depending on success + * + * @return bool + * @link http://docs.elgg.org/Access/Collections + * @see create_access_collection() + * @see update_access_collection() */ function delete_access_collection($collection_id) { + global $CONFIG; $collection_id = (int) $collection_id; - $collections = get_write_access_array(null, null, TRUE); $params = array('collection_id' => $collection_id); - if (!trigger_plugin_hook('access:collections:deletecollection', 'collection', $params, true)) { + if (!elgg_trigger_plugin_hook('access:collections:deletecollection', 'collection', $params, true)) { return false; } - if (array_key_exists($collection_id, $collections)) { - global $CONFIG; - delete_data("delete from {$CONFIG->dbprefix}access_collection_membership where access_collection_id = {$collection_id}"); - delete_data("delete from {$CONFIG->dbprefix}access_collections where id = {$collection_id}"); - return true; - } else { - return false; - } + // Deleting membership doesn't affect result of deleting ACL. + $q = "DELETE FROM {$CONFIG->dbprefix}access_collection_membership + WHERE access_collection_id = {$collection_id}"; + delete_data($q); + $q = "DELETE FROM {$CONFIG->dbprefix}access_collections + WHERE id = {$collection_id}"; + $result = delete_data($q); + + return (bool)$result; } /** * Get a specified access collection * + * @note This doesn't return the members of an access collection, + * just the database row of the actual collection. + * + * @see get_members_of_access_collection() + * * @param int $collection_id The collection ID - * @return array|false Depending on success + * + * @return object|false */ function get_access_collection($collection_id) { global $CONFIG; $collection_id = (int) $collection_id; - $get_collection = get_data_row("SELECT * FROM {$CONFIG->dbprefix}access_collections WHERE id = {$collection_id}"); + $query = "SELECT * FROM {$CONFIG->dbprefix}access_collections WHERE id = {$collection_id}"; + $get_collection = get_data_row($query); return $get_collection; } /** - * Adds a user to the specified user collection + * Adds a user to an access collection. * - * @param int $user_guid The GUID of the user to add + * Triggers the 'access:collections:add_user', 'collection' plugin hook. + * + * @param int $user_guid The GUID of the user to add * @param int $collection_id The ID of the collection to add them to - * @return true|false Depending on success + * + * @return bool + * @see update_access_collection() + * @see remove_user_from_access_collection() + * @link http://docs.elgg.org/Access/Collections */ function add_user_to_access_collection($user_guid, $collection_id) { + global $CONFIG; + $collection_id = (int) $collection_id; $user_guid = (int) $user_guid; - $collections = get_write_access_array(); - - if (!($collection = get_access_collection($collection_id))) - return false; - - if ((array_key_exists($collection_id, $collections) || $collection->owner_guid == 0) - && $user = get_user($user_guid)) { - global $CONFIG; + $user = get_user($user_guid); - $params = array( - 'collection_id' => $collection_id, - 'user_guid' => $user_guid - ); + $collection = get_access_collection($collection_id); - if (!trigger_plugin_hook('access:collections:add_user', 'collection', $params, true)) { - return false; - } + if (!($user instanceof Elgguser) || !$collection) { + return false; + } - try { - insert_data("insert into {$CONFIG->dbprefix}access_collection_membership set access_collection_id = {$collection_id}, user_guid = {$user_guid}"); - } catch (DatabaseException $e) { - // nothing. - } - return true; + $params = array( + 'collection_id' => $collection_id, + 'user_guid' => $user_guid + ); + $result = elgg_trigger_plugin_hook('access:collections:add_user', 'collection', $params, true); + if ($result == false) { + return false; } - return false; + // if someone tries to insert the same data twice, we do a no-op on duplicate key + $q = "INSERT INTO {$CONFIG->dbprefix}access_collection_membership + SET access_collection_id = $collection_id, user_guid = $user_guid + ON DUPLICATE KEY UPDATE user_guid = user_guid"; + $result = insert_data($q); + + return $result !== false; } /** - * Removes a user from an access collection + * Removes a user from an access collection. + * + * Triggers the 'access:collections:remove_user', 'collection' plugin hook. * - * @param int $user_guid The user GUID + * @param int $user_guid The user GUID * @param int $collection_id The access collection ID - * @return true|false Depending on success + * + * @return bool + * @see update_access_collection() + * @see remove_user_from_access_collection() + * @link http://docs.elgg.org/Access/Collections */ function remove_user_from_access_collection($user_guid, $collection_id) { + global $CONFIG; + $collection_id = (int) $collection_id; $user_guid = (int) $user_guid; - $collections = get_write_access_array(); + $user = get_user($user_guid); - if (!($collection = get_access_collection($collection_id))) - return false; - - if ((array_key_exists($collection_id, $collections) || $collection->owner_guid == 0) && $user = get_user($user_guid)) { - global $CONFIG; - $params = array( - 'collection_id' => $collection_id, - 'user_guid' => $user_guid - ); + $collection = get_access_collection($collection_id); - if (!trigger_plugin_hook('access:collections:remove_user', 'collection', $params, true)) { - return false; - } + if (!($user instanceof Elgguser) || !$collection) { + return false; + } - delete_data("delete from {$CONFIG->dbprefix}access_collection_membership where access_collection_id = {$collection_id} and user_guid = {$user_guid}"); - return true; + $params = array( + 'collection_id' => $collection_id, + 'user_guid' => $user_guid + ); + if (!elgg_trigger_plugin_hook('access:collections:remove_user', 'collection', $params, true)) { + return false; } - return false; + $q = "DELETE FROM {$CONFIG->dbprefix}access_collection_membership + WHERE access_collection_id = {$collection_id} + AND user_guid = {$user_guid}"; + + return (bool)delete_data($q); } /** - * Get all of a users collections + * Returns an array of database row objects of the access collections owned by $owner_guid. + * + * @param int $owner_guid The entity guid + * @param int $site_guid The GUID of the site (default: current site). * - * @param int $owner_guid The user ID - * @return true|false Depending on success + * @return array|false + * @see add_access_collection() + * @see get_members_of_access_collection() + * @link http://docs.elgg.org/Access/Collections */ -function get_user_access_collections($owner_guid) { +function get_user_access_collections($owner_guid, $site_guid = 0) { global $CONFIG; $owner_guid = (int) $owner_guid; + $site_guid = (int) $site_guid; + + if (($site_guid == 0) && (isset($CONFIG->site_guid))) { + $site_guid = $CONFIG->site_guid; + } - $collections = get_data("SELECT * FROM {$CONFIG->dbprefix}access_collections WHERE owner_guid = {$owner_guid}"); + $query = "SELECT * FROM {$CONFIG->dbprefix}access_collections + WHERE owner_guid = {$owner_guid} + AND site_guid = {$site_guid}"; + + $collections = get_data($query); return $collections; } /** - * Get all of members of a friend collection + * Get all of members of an access collection + * + * @param int $collection The collection's ID + * @param bool $idonly If set to true, will only return the members' GUIDs (default: false) * - * @param int $collection The collection's ID - * @param true|false $idonly If set to true, will only return the members' IDs (default: false) - * @return ElggUser entities if successful, false if not + * @return array ElggUser guids or entities if successful, false if not + * @see add_user_to_access_collection() + * @see http://docs.elgg.org/Access/Collections */ -function get_members_of_access_collection($collection, $idonly = false) { +function get_members_of_access_collection($collection, $idonly = FALSE) { global $CONFIG; $collection = (int)$collection; if (!$idonly) { - $query = "SELECT e.* FROM {$CONFIG->dbprefix}access_collection_membership m JOIN {$CONFIG->dbprefix}entities e ON e.guid = m.user_guid WHERE m.access_collection_id = {$collection}"; + $query = "SELECT e.* FROM {$CONFIG->dbprefix}access_collection_membership m" + . " JOIN {$CONFIG->dbprefix}entities e ON e.guid = m.user_guid" + . " WHERE m.access_collection_id = {$collection}"; $collection_members = get_data($query, "entity_row_to_elggstar"); } else { - $query = "SELECT e.guid FROM {$CONFIG->dbprefix}access_collection_membership m JOIN {$CONFIG->dbprefix}entities e ON e.guid = m.user_guid WHERE m.access_collection_id = {$collection}"; + $query = "SELECT e.guid FROM {$CONFIG->dbprefix}access_collection_membership m" + . " JOIN {$CONFIG->dbprefix}entities e ON e.guid = m.user_guid" + . " WHERE m.access_collection_id = {$collection}"; $collection_members = get_data($query); - foreach($collection_members as $key => $val) { + if (!$collection_members) { + return FALSE; + } + foreach ($collection_members as $key => $val) { $collection_members[$key] = $val->guid; } } @@ -637,93 +823,16 @@ function get_members_of_access_collection($collection, $idonly = false) { } /** - * Displays a user's access collections, using the friends/collections view - * - * @param int $owner_guid The GUID of the owning user - * @return string A formatted rendition of the collections - */ -function elgg_view_access_collections($owner_guid) { - if ($collections = get_user_access_collections($owner_guid)) { - foreach($collections as $key => $collection) { - $collections[$key]->members = get_members_of_access_collection($collection->id, true); - $collections[$key]->entities = get_user_friends($owner_guid,"",9999); - } - } - - return elgg_view('friends/collections',array('collections' => $collections)); -} - -/** - * Get entities with the specified access collection id. + * Return entities based upon access id. * - * @deprecated 1.7. Use elgg_get_entities_from_access_id() + * @param array $options Any options accepted by {@link elgg_get_entities()} and + * access_id => int The access ID of the entity. * - * @param $collection_id - * @param $entity_type - * @param $entity_subtype - * @param $owner_guid - * @param $limit - * @param $offset - * @param $order_by - * @param $site_guid - * @param $count - * @return unknown_type + * @see elgg_get_entities() + * @return mixed If count, int. If not count, array. false on errors. + * @since 1.7.0 */ -function get_entities_from_access_id($collection_id, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { - // log deprecated warning - elgg_deprecated_notice('get_entities_from_access_id() was deprecated by elgg_get_entities()!', 1.7); - - if (!$collection_id) { - return FALSE; - } - - // build the options using given parameters - $options = array(); - $options['limit'] = $limit; - $options['offset'] = $offset; - $options['count'] = $count; - - if ($entity_type) { - $options['type'] = sanitise_string($entity_type); - } - - if ($entity_subtype) { - $options['subtype'] = $entity_subtype; - } - - if ($site_guid) { - $options['site_guid'] = $site_guid; - } - - if ($order_by) { - $options['order_by'] = sanitise_string("e.time_created, $order_by"); - } - - if ((is_array($owner_guid) && (count($owner_guid)))) { - $options['owner_guids'] = array(); - foreach($owner_guid as $guid) { - $options['owner_guids'][] = $guid; - } - } - - if ($site_guid) { - $options['site_guid'] = $site_guid; - } - - $options['access_id'] = $collection_id; - - return elgg_get_entities_from_access_id($options); -} - -/** - * Retrieve entities for a given access collection - * - * @param int $collection_id - * @param array $options @see elgg_get_entities() - * @return array - * @since 1.7 - */ -function elgg_get_entities_from_access_id(array $options=array()) { +function elgg_get_entities_from_access_id(array $options = array()) { // restrict the resultset to access collection provided if (!isset($options['access_id'])) { return FALSE; @@ -748,70 +857,101 @@ function elgg_get_entities_from_access_id(array $options=array()) { /** * Lists entities from an access collection * - * @param $collection_id - * @param $entity_type - * @param $entity_subtype - * @param $owner_guid - * @param $limit - * @param $fullview - * @param $viewtypetoggle - * @param $pagination - * @return str - */ -function list_entities_from_access_id($collection_id, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = true) { - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = get_entities_from_access_id($collection_id, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", 0, true); - $entities = get_entities_from_access_id($collection_id, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", 0, false); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); + * @param array $options See elgg_list_entities() and elgg_get_entities_from_access_id() + * + * @see elgg_list_entities() + * @see elgg_get_entities_from_access_id() + * + * @return string + */ +function elgg_list_entities_from_access_id(array $options = array()) { + return elgg_list_entities($options, 'elgg_get_entities_from_access_id'); } /** - * Return a humanreadable version of an entity's access level + * Return the name of an ACCESS_* constant or a access collection, + * but only if the user has write access on that ACL. * - * @param $entity_accessid (int) The entity's access id - * @return string e.g. Public, Private etc - **/ -function get_readable_access_level($entity_accessid){ - $access = (int) $entity_accessid; + * @warning This function probably doesn't work how it's meant to. + * + * @param int $entity_access_id The entity's access id + * + * @return string 'Public', 'Private', etc. + * @since 1.7.0 + * @todo I think this probably wants get_access_array() instead of get_write_access_array(), + * but those two functions return different types of arrays. + */ +function get_readable_access_level($entity_access_id) { + $access = (int) $entity_access_id; + //get the access level for object in readable string $options = get_write_access_array(); - foreach($options as $key => $option) { - if($key == $access){ - $entity_acl = htmlentities($option, ENT_QUOTES, 'UTF-8'); - return $entity_acl; - break; - } + + if (array_key_exists($access, $options)) { + return $options[$access]; } - return false; + + // return 'Limited' if the user does not have access to the access collection + return elgg_echo('access:limited:label'); } /** * Set if entity access system should be ignored. * + * The access system will not return entities in any getter + * functions if the user doesn't have access. + * + * @internal For performance reasons this is done at the database access clause level. + * + * @tip Use this to access entities in automated scripts + * when no user is logged in. + * + * @note This clears the access cache. + * + * @warning This will not show disabled entities. + * Use {@link access_show_hidden_entities()} to access disabled entities. + * + * @param bool $ignore If true, disables all access checks. + * * @return bool Previous ignore_access setting. + * @since 1.7.0 + * @see http://docs.elgg.org/Access/IgnoreAccess + * @see elgg_get_ignore_access() */ function elgg_set_ignore_access($ignore = true) { + $cache = _elgg_get_access_cache(); + $cache->clear(); $elgg_access = elgg_get_access_object(); - return $elgg_access->set_ignore_access($ignore); + return $elgg_access->setIgnoreAccess($ignore); } /** * Get current ignore access setting. * * @return bool + * @since 1.7.0 + * @see http://docs.elgg.org/Access/IgnoreAccess + * @see elgg_set_ignore_access() */ function elgg_get_ignore_access() { - return elgg_get_access_object()->get_ignore_access(); + return elgg_get_access_object()->getIgnoreAccess(); } /** - * Decides if the access system is being ignored. + * Decides if the access system should be ignored for a user. + * + * Returns true (meaning ignore access) if either of these 2 conditions are true: + * 1) an admin user guid is passed to this function. + * 2) {@link elgg_get_ignore_access()} returns true. + * + * @see elgg_set_ignore_access() + * + * @param int $user_guid The user to check against. * * @return bool + * @since 1.7.0 */ -function elgg_check_access_overrides($user_guid = null) { +function elgg_check_access_overrides($user_guid = 0) { if (!$user_guid || $user_guid <= 0) { $is_admin = false; } else { @@ -824,7 +964,12 @@ function elgg_check_access_overrides($user_guid = null) { /** * Returns the ElggAccess object. * + * // @todo comment is incomplete + * This is used to + * * @return ElggAccess + * @since 1.7.0 + * @access private */ function elgg_get_access_object() { static $elgg_access; @@ -836,12 +981,23 @@ function elgg_get_access_object() { return $elgg_access; } -global $init_finished; +/** + * A flag to set if Elgg's access initialization is finished. + * + * @global bool $init_finished + * @access private + * @todo This is required to tell the access system to start caching because + * calls are made while in ignore access mode and before the user is logged in. + */ $init_finished = false; /** * A quick and dirty way to make sure the access permissions have been correctly set up * + * @elgg_event_handler init system + * @todo Invesigate + * + * @return void */ function access_init() { global $init_finished; @@ -849,12 +1005,35 @@ function access_init() { } /** - * Override permissions system + * Overrides the access system if appropriate. * + * Allows admin users and calls after {@link elgg_set_ignore_access} to + * bypass the access system. + * + * Registered for the 'permissions_check', 'all' and the + * 'container_permissions_check', 'all' plugin hooks. + * + * Returns true to override the access system or null if no change is needed. + * + * @param string $hook + * @param string $type + * @param bool $value + * @param array $params * @return true|null + * @access private */ -function elgg_override_permissions_hook($hook, $type, $returnval, $params) { - $user_guid = get_loggedin_userid(); +function elgg_override_permissions($hook, $type, $value, $params) { + $user = elgg_extract('user', $params); + if ($user) { + $user_guid = $user->getGUID(); + } else { + $user_guid = elgg_get_logged_in_user_guid(); + } + + // don't do this so ignore access still works with no one logged in + //if (!$user instanceof ElggUser) { + // return false; + //} // check for admin if ($user_guid && elgg_is_admin_user($user_guid)) { @@ -870,9 +1049,30 @@ function elgg_override_permissions_hook($hook, $type, $returnval, $params) { return NULL; } -// This function will let us know when 'init' has finished -register_elgg_event_handler('init', 'system', 'access_init', 9999); +/** + * Runs unit tests for the entities object. + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array + * + * @access private + */ +function access_test($hook, $type, $value, $params) { + global $CONFIG; + + $value[] = $CONFIG->path . 'engine/tests/api/access_collections.php'; + return $value; +} + +// Tell the access functions the system has booted, plugins are loaded, +// and the user is logged in so it can start caching +elgg_register_event_handler('ready', 'system', 'access_init'); // For overrided permissions -register_plugin_hook('permissions_check', 'all', 'elgg_override_permissions_hook'); -register_plugin_hook('container_permissions_check', 'all', 'elgg_override_permissions_hook'); +elgg_register_plugin_hook_handler('permissions_check', 'all', 'elgg_override_permissions'); +elgg_register_plugin_hook_handler('container_permissions_check', 'all', 'elgg_override_permissions'); + +elgg_register_plugin_hook_handler('unit_test', 'system', 'access_test');
\ No newline at end of file diff --git a/engine/lib/actions.php b/engine/lib/actions.php index 278194ac3..8047914ac 100644 --- a/engine/lib/actions.php +++ b/engine/lib/actions.php @@ -1,90 +1,153 @@ <?php /** - * Elgg actions - * Allows system modules to specify actions + * Elgg Actions * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * Actions are one of the primary controllers (The C in MVC) in Elgg. They are + * registered by {@link register_elgg_action()} and are called by URL + * http://elggsite.org/action/action_name. For URLs, a rewrite rule in + * .htaccess passes the action name to engine/handlers/action_handler.php, + * which dispatches the request for the action. + * + * An action name must be registered to a file in the system. Core actions are + * found in /actions/ and plugin actions are usually under /mod/<plugin>/actions/. + * It is recommended that actions be namespaced to avoid collisions. + * + * All actions require security tokens. Using the {@elgg_view input/form} view + * will automatically add tokens as hidden inputs as will the elgg_view_form() + * function. To manually add hidden inputs, use the {@elgg_view input/securitytoken} view. + * + * To include security tokens for actions called via GET, use + * {@link elgg_add_security_tokens_to_url()} or specify is_action as true when + * using {@lgg_view output/url}. + * + * Action tokens can be manually generated by using {@link generate_action_token()}. + * + * @tip When registered, actions can be restricted to logged in or admin users. + * + * @tip Action URLs should be called with a trailing / to prevent 301 redirects. + * + * @package Elgg.Core + * @subpackage Actions + * @link http://docs.elgg.org/Actions + * @link http://docs.elgg.org/Actions/Tokens */ -// Action setting and run ************************************************* - /** -* Loads an action script, if it exists, then forwards elsewhere -* -* @param string $action The requested action -* @param string $forwarder Optionally, the location to forward to -*/ - + * Perform an action. + * + * This function executes the action with name $action as registered + * by {@link elgg_register_action()}. + * + * The plugin hook 'action', $action_name will be triggered before the action + * is executed. If a handler returns false, it will prevent the action script + * from being called. + * + * @note If an action isn't registered in the system or is registered + * to an unavailable file the user will be forwarded to the site front + * page and an error will be emitted via {@link register_error()}. + * + * @warning All actions require {@link http://docs.elgg.org/Actions/Tokens Action Tokens}. + * + * @param string $action The requested action + * @param string $forwarder Optionally, the location to forward to + * + * @link http://docs.elgg.org/Actions + * @see elgg_register_action() + * + * @return void + * @access private + */ function action($action, $forwarder = "") { global $CONFIG; - // set GET params - elgg_set_input_from_uri(); + $action = rtrim($action, '/'); - // @todo REMOVE THESE EXCEPTIONS IN 1.8. - // These are only to provide a way to disable plugins that overwrite core - // UI without tokens. (And for installation because of session_id problems) + // @todo REMOVE THESE ONCE #1509 IS IN PLACE. + // Allow users to disable plugins without a token in order to + // remove plugins that are incompatible. + // Logout for convenience. + // file/download (see #2010) $exceptions = array( - 'systemsettings/install', 'admin/plugins/disable', - 'logout' + 'logout', + 'file/download', ); if (!in_array($action, $exceptions)) { - // All actions require a token. - action_gatekeeper(); + action_gatekeeper($action); } - $forwarder = str_replace($CONFIG->url, "", $forwarder); + $forwarder = str_replace(elgg_get_site_url(), "", $forwarder); $forwarder = str_replace("http://", "", $forwarder); $forwarder = str_replace("@", "", $forwarder); - - if (substr($forwarder,0,1) == "/") { - $forwarder = substr($forwarder,1); + if (substr($forwarder, 0, 1) == "/") { + $forwarder = substr($forwarder, 1); } - if (isset($CONFIG->actions[$action])) { - if ((isadminloggedin()) || (!$CONFIG->actions[$action]['admin'])) { - if ($CONFIG->actions[$action]['public'] || $_SESSION['id'] != -1) { - - // Trigger action event TODO: This is only called before the primary action is called. We need to rethink actions for 1.5 - $event_result = true; - $event_result = trigger_plugin_hook('action', $action, null, $event_result); - - // Include action - // Event_result being false doesn't produce an error - - // since i assume this will be handled in the hook itself. - // TODO make this better! - if ($event_result) { - if (!include($CONFIG->actions[$action]['file'])) { - register_error(sprintf(elgg_echo('actionundefined'),$action)); - } - } - } else { - register_error(elgg_echo('actionloggedout')); + if (!isset($CONFIG->actions[$action])) { + register_error(elgg_echo('actionundefined', array($action))); + } elseif (!elgg_is_admin_logged_in() && ($CONFIG->actions[$action]['access'] === 'admin')) { + register_error(elgg_echo('actionunauthorized')); + } elseif (!elgg_is_logged_in() && ($CONFIG->actions[$action]['access'] !== 'public')) { + register_error(elgg_echo('actionloggedout')); + } else { + // Returning falsy doesn't produce an error + // We assume this will be handled in the hook itself. + if (elgg_trigger_plugin_hook('action', $action, null, true)) { + if (!include($CONFIG->actions[$action]['file'])) { + register_error(elgg_echo('actionnotfound', array($action))); } } - } else { - register_error(sprintf(elgg_echo('actionundefined'),$action)); } - forward($CONFIG->url . $forwarder); + $forwarder = empty($forwarder) ? REFERER : $forwarder; + forward($forwarder); } /** - * Registers a particular action in memory + * Registers an action. + * + * Actions are registered to a script in the system and are executed + * either by the URL http://elggsite.org/action/action_name/. * - * @param string $action The name of the action (eg "register", "account/settings/save") - * @param boolean $public Can this action be accessed by people not logged into the system? - * @param string $filename Optionally, the filename where this action is located - * @param boolean $admin_only Whether this action is only available to admin users. + * $filename must be the full path of the file to register, or a path relative + * to the core actions/ dir. + * + * Actions should be namedspaced for your plugin. Example: + * <code> + * elgg_register_action('myplugin/save_settings', ...); + * </code> + * + * @tip Put action files under the actions/<plugin_name> directory of your plugin. + * + * @tip You don't need to include engine/start.php in your action files. + * + * @internal Actions are saved in $CONFIG->actions as an array in the form: + * <code> + * array( + * 'file' => '/location/to/file.php', + * 'access' => 'public', 'logged_in', or 'admin' + * ) + * </code> + * + * @param string $action The name of the action (eg "register", "account/settings/save") + * @param string $filename Optionally, the filename where this action is located. If not specified, + * will assume the action is in elgg/actions/<action>.php + * @param string $access Who is allowed to execute this action: public, logged_in, admin. + * (default: logged_in) + * + * @see action() + * @see http://docs.elgg.org/Actions + * + * @return bool */ -function register_action($action, $public = false, $filename = "", $admin_only = false) { +function elgg_register_action($action, $filename = "", $access = 'logged_in') { global $CONFIG; + // plugins are encouraged to call actions with a trailing / to prevent 301 + // redirects but we store the actions without it + $action = rtrim($action, '/'); + if (!isset($CONFIG->actions)) { $CONFIG->actions = array(); } @@ -98,48 +161,92 @@ function register_action($action, $public = false, $filename = "", $admin_only = $filename = $path . "actions/" . $action . ".php"; } - $CONFIG->actions[$action] = array('file' => $filename, 'public' => $public, 'admin' => $admin_only); + $CONFIG->actions[$action] = array( + 'file' => $filename, + 'access' => $access, + ); return true; } /** - * Actions to perform on initialisation + * Unregisters an action * - * @param string $event Events API required parameters - * @param string $object_type Events API required parameters - * @param string $object Events API required parameters + * @param string $action Action name + * @return bool + * @since 1.8.1 */ -function actions_init($event, $object_type, $object) { - register_action("error"); - return true; +function elgg_unregister_action($action) { + global $CONFIG; + + if (isset($CONFIG->actions[$action])) { + unset($CONFIG->actions[$action]); + return true; + } else { + return false; + } +} + +/** + * Is the token timestamp within acceptable range? + * + * @param int $ts timestamp from the CSRF token + * + * @return bool + */ +function _elgg_validate_token_timestamp($ts) { + $action_token_timeout = elgg_get_config('action_token_timeout'); + // default is 2 hours + $timeout = ($action_token_timeout !== null) ? $action_token_timeout : 2; + + $hour = 60 * 60; + $timeout = $timeout * $hour; + $now = time(); + + // Validate time to ensure its not crazy + return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout)); } /** - * Validate an action token, returning true if valid and false if not + * Validate an action token. + * + * Calls to actions will automatically validate tokens. If tokens are not + * present or invalid, the action will be denied and the user will be redirected. + * + * Plugin authors should never have to manually validate action tokens. + * + * @param bool $visibleerrors Emit {@link register_error()} errors on failure? + * @param mixed $token The token to test against. Default: $_REQUEST['__elgg_token'] + * @param mixed $ts The time stamp to test against. Default: $_REQUEST['__elgg_ts'] * - * @return unknown + * @return bool + * @see generate_action_token() + * @link http://docs.elgg.org/Actions/Tokens + * @access private */ -function validate_action_token($visibleerrors = true) { - $token = get_input('__elgg_token'); - $ts = get_input('__elgg_ts'); +function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) { + if (!$token) { + $token = get_input('__elgg_token'); + } + + if (!$ts) { + $ts = get_input('__elgg_ts'); + } + $session_id = session_id(); if (($token) && ($ts) && ($session_id)) { // generate token, check with input and forward if invalid - $generated_token = generate_action_token($ts); + $required_token = generate_action_token($ts); // Validate token - if ($token == $generated_token) { - $hour = 60*60; - $now = time(); - - // Validate time to ensure its not crazy - if (($ts>$now-$hour) && ($ts<$now+$hour)) { + if ($token == $required_token) { + + if (_elgg_validate_token_timestamp($ts)) { // We have already got this far, so unless anything - // else says something to the contry we assume we're ok + // else says something to the contrary we assume we're ok $returnval = true; - $returnval = trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', array( + $returnval = elgg_trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', array( 'token' => $token, 'time' => $ts ), $returnval); @@ -150,66 +257,126 @@ function validate_action_token($visibleerrors = true) { register_error(elgg_echo('actiongatekeeper:pluginprevents')); } } else if ($visibleerrors) { - register_error(elgg_echo('actiongatekeeper:timeerror')); + // this is necessary because of #5133 + if (elgg_is_xhr()) { + register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); + } else { + register_error(elgg_echo('actiongatekeeper:timeerror')); + } } } else if ($visibleerrors) { - register_error(elgg_echo('actiongatekeeper:tokeninvalid')); + // this is necessary because of #5133 + if (elgg_is_xhr()) { + register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); + } else { + register_error(elgg_echo('actiongatekeeper:tokeninvalid')); + } + } + } else { + if (! empty($_SERVER['CONTENT_LENGTH']) && empty($_POST)) { + // The size of $_POST or uploaded file has exceed the size limit + $error_msg = elgg_trigger_plugin_hook('action_gatekeeper:upload_exceeded_msg', 'all', array( + 'post_size' => $_SERVER['CONTENT_LENGTH'], + 'visible_errors' => $visibleerrors, + ), elgg_echo('actiongatekeeper:uploadexceeded')); + } else { + $error_msg = elgg_echo('actiongatekeeper:missingfields'); + } + if ($visibleerrors) { + register_error($error_msg); } - } - else if ($visibleerrors) { - register_error(elgg_echo('actiongatekeeper:missingfields')); } return FALSE; } /** -* Action gatekeeper. -* This function verifies form input for security features (like a generated token), and forwards -* the page if they are invalid. -* -* Place at the head of actions. -*/ -function action_gatekeeper() { - if (validate_action_token()) { - return TRUE; + * Validates the presence of action tokens. + * + * This function is called for all actions. If action tokens are missing, + * the user will be forwarded to the site front page and an error emitted. + * + * This function verifies form input for security features (like a generated token), + * and forwards if they are invalid. + * + * @param string $action The action being performed + * + * @return mixed True if valid or redirects. + * @access private + */ +function action_gatekeeper($action) { + if ($action === 'login') { + if (validate_action_token(false)) { + return true; + } + + $token = get_input('__elgg_token'); + $ts = (int)get_input('__elgg_ts'); + if ($token && _elgg_validate_token_timestamp($ts)) { + // The tokens are present and the time looks valid: this is probably a mismatch due to the + // login form being on a different domain. + register_error(elgg_echo('actiongatekeeper:crosssitelogin')); + + + forward('login', 'csrf'); + } + + // let the validator send an appropriate msg + validate_action_token(); + + } elseif (validate_action_token()) { + return true; } - forward(); - exit; + forward(REFERER, 'csrf'); } /** - * Generate a token for the current user suitable for being placed in a hidden field in action forms. + * Generate an action token. + * + * Action tokens are based on timestamps as returned by {@link time()}. + * They are valid for one hour. + * + * Action tokens should be passed to all actions name __elgg_ts and __elgg_token. + * + * @warning Action tokens are required for all actions. * * @param int $timestamp Unix timestamp + * + * @see @elgg_view input/securitytoken + * @see @elgg_view input/form + * @example actions/manual_tokens.php + * + * @return string|false + * @access private */ function generate_action_token($timestamp) { - // Get input values $site_secret = get_site_secret(); - - // Current session id $session_id = session_id(); - - // Get user agent - $ua = $_SERVER['HTTP_USER_AGENT']; - // Session token $st = $_SESSION['__elgg_session']; if (($site_secret) && ($session_id)) { - return md5($site_secret.$timestamp.$session_id.$ua.$st); + return md5($site_secret . $timestamp . $session_id . $st); } return FALSE; } /** - * Initialise the site secret. + * Initialise the site secret (32 bytes: "z" to indicate format + 186-bit key in Base64 URL). + * + * Used during installation and saves as a datalist. * + * Note: Old secrets were hex encoded. + * + * @return mixed The site secret hash or false + * @access private + * @todo Move to better file. */ function init_site_secret() { - $secret = md5(rand().microtime()); + $secret = 'z' . ElggCrypto::getRandomString(31); + if (datalist_set('__site_secret__', $secret)) { return $secret; } @@ -218,8 +385,13 @@ function init_site_secret() { } /** - * Retrieve the site secret. + * Returns the site secret. * + * Used to generate difficult to guess hashes for sessions and action tokens. + * + * @return string Site secret. + * @access private + * @todo Move to better file. */ function get_site_secret() { $secret = datalist_get('__site_secret__'); @@ -230,5 +402,148 @@ function get_site_secret() { return $secret; } -// Register some actions *************************************************** -register_elgg_event_handler("init","system","actions_init"); +/** + * Get the strength of the site secret + * + * @return string "strong", "moderate", or "weak" + * @access private + */ +function _elgg_get_site_secret_strength() { + $secret = get_site_secret(); + if ($secret[0] !== 'z') { + $rand_max = getrandmax(); + if ($rand_max < pow(2, 16)) { + return 'weak'; + } + if ($rand_max < pow(2, 32)) { + return 'moderate'; + } + } + return 'strong'; +} + +/** + * Check if an action is registered and its script exists. + * + * @param string $action Action name + * + * @return bool + * @since 1.8.0 + */ +function elgg_action_exists($action) { + global $CONFIG; + + return (isset($CONFIG->actions[$action]) && file_exists($CONFIG->actions[$action]['file'])); +} + +/** + * Checks whether the request was requested via ajax + * + * @return bool whether page was requested via ajax + * @since 1.8.0 + */ +function elgg_is_xhr() { + return isset($_SERVER['HTTP_X_REQUESTED_WITH']) + && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest' || + get_input('X-Requested-With') === 'XMLHttpRequest'; +} + +/** + * Catch calls to forward() in ajax request and force an exit. + * + * Forces response is json of the following form: + * <pre> + * { + * "current_url": "the.url.we/were/coming/from", + * "forward_url": "the.url.we/were/going/to", + * "system_messages": { + * "messages": ["msg1", "msg2", ...], + * "errors": ["err1", "err2", ...] + * }, + * "status": -1 //or 0 for success if there are no error messages present + * } + * </pre> + * where "system_messages" is all message registers at the point of forwarding + * + * @param string $hook + * @param string $type + * @param string $reason + * @param array $params + * @return void + * @access private + */ +function ajax_forward_hook($hook, $type, $reason, $params) { + if (elgg_is_xhr()) { + // always pass the full structure to avoid boilerplate JS code. + $params = array( + 'output' => '', + 'status' => 0, + 'system_messages' => array( + 'error' => array(), + 'success' => array() + ) + ); + + //grab any data echo'd in the action + $output = ob_get_clean(); + + //Avoid double-encoding in case data is json + $json = json_decode($output); + if (isset($json)) { + $params['output'] = $json; + } else { + $params['output'] = $output; + } + + //Grab any system messages so we can inject them via ajax too + $system_messages = system_messages(NULL, ""); + + if (isset($system_messages['success'])) { + $params['system_messages']['success'] = $system_messages['success']; + } + + if (isset($system_messages['error'])) { + $params['system_messages']['error'] = $system_messages['error']; + $params['status'] = -1; + } + + // Check the requester can accept JSON responses, if not fall back to + // returning JSON in a plain-text response. Some libraries request + // JSON in an invisible iframe which they then read from the iframe, + // however some browsers will not accept the JSON MIME type. + if (stripos($_SERVER['HTTP_ACCEPT'], 'application/json') === FALSE) { + header("Content-type: text/plain"); + } else { + header("Content-type: application/json"); + } + + echo json_encode($params); + exit; + } +} + +/** + * Buffer all output echo'd directly in the action for inclusion in the returned JSON. + * @return void + * @access private + */ +function ajax_action_hook() { + if (elgg_is_xhr()) { + ob_start(); + } +} + +/** + * Initialize some ajaxy actions features + * @access private + */ +function actions_init() { + elgg_register_action('security/refreshtoken', '', 'public'); + + elgg_register_simplecache_view('js/languages/en'); + + elgg_register_plugin_hook_handler('action', 'all', 'ajax_action_hook'); + elgg_register_plugin_hook_handler('forward', 'all', 'ajax_forward_hook'); +} + +elgg_register_event_handler('init', 'system', 'actions_init'); diff --git a/engine/lib/activity.php b/engine/lib/activity.php deleted file mode 100644 index 51b083531..000000000 --- a/engine/lib/activity.php +++ /dev/null @@ -1,169 +0,0 @@ -<?php -/** - * Elgg activity stream. - * Functions for listening for and generating the rich activity stream from the - * system log. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - -/** - * Construct and execute the query required for the activity stream. - * - * @param int $limit Limit the query. - * @param int $offset Execute from the given object - * @param mixed $type A type, or array of types to look for. Note: This is how they appear in the SYSTEM LOG. - * @param mixed $subtype A subtype, or array of types to look for. Note: This is how they appear in the SYSTEM LOG. - * @param mixed $owner_guid The guid or a collection of GUIDs - * @param string $owner_relationship If defined, the relationship between $owner_guid and the entity owner_guid - so "is $owner_guid $owner_relationship with $entity->owner_guid" - * @return array An array of system log entries. - */ -function get_activity_stream_data($limit = 10, $offset = 0, $type = "", $subtype = "", $owner_guid = "", $owner_relationship = "") { - global $CONFIG; - - $limit = (int)$limit; - $offset = (int)$offset; - - if ($type) { - if (!is_array($type)) { - $type = array(sanitise_string($type)); - } else { - foreach ($type as $k => $v) { - $type[$k] = sanitise_string($v); - } - } - } - - if ($subtype) { - if (!is_array($subtype)) { - $subtype = array(sanitise_string($subtype)); - } else { - foreach ($subtype as $k => $v) { - $subtype[$k] = sanitise_string($v); - } - } - } - - if ($owner_guid) { - if (is_array($owner_guid)) { - foreach ($owner_guid as $k => $v) { - $owner_guid[$k] = (int)$v; - } - } else { - $owner_guid = array((int)$owner_guid); - } - } - - $owner_relationship = sanitise_string($owner_relationship); - - // Get a list of possible views - $activity_events= array(); - $activity_views = array_merge(elgg_view_tree('activity', 'default'), elgg_view_tree('river', 'default')); // Join activity with river - - $done = array(); - - foreach ($activity_views as $view) { - $fragments = explode('/', $view); - $tmp = explode('/',$view, 2); - $tmp = $tmp[1]; - - if ((isset($fragments[0])) && (($fragments[0] == 'river') || ($fragments[0] == 'activity')) - && (!in_array($tmp, $done))) { - - if (isset($fragments[1])) { - $f = array(); - for ($n = 1; $n < count($fragments); $n++) { - $val = sanitise_string($fragments[$n]); - switch($n) { - case 1: $key = 'type'; break; - case 2: $key = 'subtype'; break; - case 3: $key = 'event'; break; - } - $f[$key] = $val; - } - - // Filter result based on parameters - $add = true; - if ($type) { - if (!in_array($f['type'], $type)) { - $add = false; - } - } - if (($add) && ($subtype)) { - if (!in_array($f['subtype'], $subtype)) { - $add = false; - } - } - if (($add) && ($event)) { - if (!in_array($f['event'], $event)) { - $add = false; - } - } - - if ($add) { - $activity_events[] = $f; - } - } - - $done[] = $tmp; - } - } - - $n = 0; - foreach ($activity_events as $details) { - // Get what we're talking about - if ($details['subtype'] == 'default') { - $details['subtype'] = ''; - } - - if (($details['type']) && ($details['event'])) { - if ($n > 0) { - $obj_query .= " or "; - } - - $access = ""; - if ($details['type']!='relationship') { - $access = " and " . get_access_sql_suffix('sl'); - } - - $obj_query .= "( sl.object_type='{$details['type']}' - AND sl.object_subtype='{$details['subtype']}' - AND sl.event='{$details['event']}' $access )"; - - $n++; - } - } - - // User - if ((count($owner_guid)) && ($owner_guid[0] != 0)) { - $user = " and sl.performed_by_guid in (".implode(',', $owner_guid).")"; - - if ($owner_relationship) { - $friendsarray = ""; - if ($friends = elgg_get_entities_from_relationship(array( - 'relationship' => $owner_relationship, - 'relationship_guid' => $owner_guid[0], - 'inverse_relationship' => FALSE, - 'types' => 'user', - 'subtypes' => $subtype, - 'limit' => 9999)) - ) { - - $friendsarray = array(); - foreach($friends as $friend) { - $friendsarray[] = $friend->getGUID(); - } - - $user = " and sl.performed_by_guid in (" . implode(',', $friendsarray).")"; - } - } - } - - $query = "SELECT sl.* FROM {$CONFIG->dbprefix}system_log sl - WHERE 1 $user AND ($obj_query) - ORDER BY sl.time_created desc limit $offset, $limit"; - return get_data($query); -} diff --git a/engine/lib/admin.php b/engine/lib/admin.php index c8ca90a2f..f36f29668 100644 --- a/engine/lib/admin.php +++ b/engine/lib/admin.php @@ -1,156 +1,663 @@ <?php /** * Elgg admin functions. - * Functions for adding and manipulating options on the admin panel. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * Admin menu items + * Elgg has a convenience function for adding menu items to the sidebar of the + * admin area. @see elgg_register_admin_menu_item() + * + * Admin pages + * Plugins no not need to provide their own page handler to add a page to the + * admin area. A view placed at admin/<section>/<subsection> can be access + * at http://example.org/admin/<section>/<subsection>. The title of the page + * will be elgg_echo('admin:<section>:<subsection>'). For an example of how to + * add a page to the admin area, see the diagnostics plugin. + * + * Admin notices + * System messages (success and error messages) are used in both the main site + * and the admin area. There is a special presistent message for the admin area + * called an admin notice. It should be used when a plugin requires an + * administrator to take an action. An example is the categories plugin + * requesting that the administrator set site categories after the plugin has + * been activated. @see elgg_add_admin_notice() + * + * + * @package Elgg.Core + * @subpackage Admin */ +/** + * Get the admin users + * + * @param array $options Options array, @see elgg_get_entities() for parameters + * + * @return mixed Array of admin users or false on failure. If a count, returns int. + * @since 1.8.0 + */ +function elgg_get_admins(array $options = array()) { + global $CONFIG; + + if (isset($options['joins'])) { + if (!is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } + $options['joins'][] = "join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid"; + } else { + $options['joins'] = array("join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid"); + } + + if (isset($options['wheres'])) { + if (!is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } + $options['wheres'][] = "u.admin = 'yes'"; + } else { + $options['wheres'][] = "u.admin = 'yes'"; + } + + return elgg_get_entities($options); +} /** - * Register an admin page with the admin panel. - * This function extends the view "admin/main" with the provided view. This view should provide a description - * and either a control or a link to. - * - * Usage: - * - To add a control to the main admin panel then extend admin/main - * - To add a control to a new page create a page which renders a view admin/subpage - * (where subpage is your new page - - * nb. some pages already exist that you can extend), extend the main view to point to it, - * and add controls to your new view. - * - * At the moment this is essentially a wrapper around elgg_extend_view(). - * - * @param string $new_admin_view The view associated with the control you're adding - * @param string $view The view to extend, by default this is 'admin/main'. - * @param int $priority Optional priority to govern the appearance in the list. + * Write a persistent message to the admin view. + * Useful to alert the admin to take a certain action. + * The id is a unique ID that can be cleared once the admin + * completes the action. + * + * eg: add_admin_notice('twitter_services_no_api', + * 'Before your users can use Twitter services on this site, you must set up + * the Twitter API key in the <a href="link">Twitter Services Settings</a>'); + * + * @param string $id A unique ID that your plugin can remember + * @param string $message Body of the message + * + * @return bool + * @since 1.8.0 */ -function extend_elgg_admin_page( $new_admin_view, $view = 'admin/main', $priority = 500) { - return elgg_extend_view($view, $new_admin_view, $priority); +function elgg_add_admin_notice($id, $message) { + if ($id && $message) { + if (elgg_admin_notice_exists($id)) { + return false; + } + + // need to handle when no one is logged in + $old_ia = elgg_set_ignore_access(true); + + $admin_notice = new ElggObject(); + $admin_notice->subtype = 'admin_notice'; + // admins can see ACCESS_PRIVATE but no one else can. + $admin_notice->access_id = ACCESS_PRIVATE; + $admin_notice->admin_notice_id = $id; + $admin_notice->description = $message; + + $result = $admin_notice->save(); + + elgg_set_ignore_access($old_ia); + + return (bool)$result; + } + + return false; } /** - * Initialise the admin page. + * Remove an admin notice by ID. + * + * eg In actions/twitter_service/save_settings: + * if (is_valid_twitter_api_key()) { + * delete_admin_notice('twitter_services_no_api'); + * } + * + * @param string $id The unique ID assigned in add_admin_notice() + * + * @return bool + * @since 1.8.0 + */ +function elgg_delete_admin_notice($id) { + if (!$id) { + return FALSE; + } + $result = TRUE; + $notices = elgg_get_entities_from_metadata(array( + 'metadata_name' => 'admin_notice_id', + 'metadata_value' => $id + )); + + if ($notices) { + // in case a bad plugin adds many, let it remove them all at once. + foreach ($notices as $notice) { + $result = ($result && $notice->delete()); + } + return $result; + } + return FALSE; +} + +/** + * Get admin notices. An admin must be logged in since the notices are private. + * + * @param int $limit Limit + * + * @return array Array of admin notices + * @since 1.8.0 + */ +function elgg_get_admin_notices($limit = 10) { + return elgg_get_entities_from_metadata(array( + 'type' => 'object', + 'subtype' => 'admin_notice', + 'limit' => $limit + )); +} + +/** + * Check if an admin notice is currently active. + * + * @param string $id The unique ID used to register the notice. + * + * @return bool + * @since 1.8.0 + */ +function elgg_admin_notice_exists($id) { + $old_ia = elgg_set_ignore_access(true); + $notice = elgg_get_entities_from_metadata(array( + 'type' => 'object', + 'subtype' => 'admin_notice', + 'metadata_name_value_pair' => array('name' => 'admin_notice_id', 'value' => $id) + )); + elgg_set_ignore_access($old_ia); + + return ($notice) ? TRUE : FALSE; +} + +/** + * Add an admin area section or child section. + * This is a wrapper for elgg_register_menu_item(). + * + * Used in conjuction with http://elgg.org/admin/section_id/child_section style + * page handler. See the documentation at the top of this file for more details + * on that. + * + * The text of the menu item is obtained from elgg_echo(admin:$parent_id:$menu_id) + * + * This function handles registering the parent if it has not been registered. + * + * @param string $section The menu section to add to + * @param string $menu_id The unique ID of section + * @param string $parent_id If a child section, the parent section id + * @param int $priority The menu item priority + * + * @return bool + * @since 1.8.0 + */ +function elgg_register_admin_menu_item($section, $menu_id, $parent_id = NULL, $priority = 100) { + + // make sure parent is registered + if ($parent_id && !elgg_is_menu_item_registered('page', $parent_id)) { + elgg_register_admin_menu_item($section, $parent_id); + } + + // in the admin section parents never have links + if ($parent_id) { + $href = "admin/$parent_id/$menu_id"; + } else { + $href = NULL; + } + + $name = $menu_id; + if ($parent_id) { + $name = "$parent_id:$name"; + } + + return elgg_register_menu_item('page', array( + 'name' => $name, + 'href' => $href, + 'text' => elgg_echo("admin:$name"), + 'context' => 'admin', + 'parent_name' => $parent_id, + 'priority' => $priority, + 'section' => $section + )); +} + +/** + * Initialize the admin backend. + * @return void + * @access private */ function admin_init() { - // Add plugin main menu option (last) - extend_elgg_admin_page('admin/main_opt/statistics', 'admin/main'); - extend_elgg_admin_page('admin/main_opt/site', 'admin/main'); - extend_elgg_admin_page('admin/main_opt/user', 'admin/main'); - extend_elgg_admin_page('admin/main_opt/plugins', 'admin/main', 999); // Always last - - register_action('admin/user/ban', false, "", true); - register_action('admin/user/unban', false, "", true); - register_action('admin/user/delete', false, "", true); - register_action('admin/user/resetpassword', false, "", true); - register_action('admin/user/makeadmin', false, "", true); - register_action('admin/user/removeadmin', false, "", true); - - // Register some actions - register_action('admin/site/update_basic', false, "", true); // Register basic site admin action - - // Page handler - register_page_handler('admin','admin_settings_page_handler'); - -// if (isadminloggedin()) { -// global $is_admin; -// $is_admin = true; -// } + elgg_register_action('admin/user/ban', '', 'admin'); + elgg_register_action('admin/user/unban', '', 'admin'); + elgg_register_action('admin/user/delete', '', 'admin'); + elgg_register_action('admin/user/resetpassword', '', 'admin'); + elgg_register_action('admin/user/makeadmin', '', 'admin'); + elgg_register_action('admin/user/removeadmin', '', 'admin'); + + elgg_register_action('admin/site/update_basic', '', 'admin'); + elgg_register_action('admin/site/update_advanced', '', 'admin'); + elgg_register_action('admin/site/flush_cache', '', 'admin'); + elgg_register_action('admin/site/unlock_upgrade', '', 'admin'); + elgg_register_action('admin/site/regenerate_secret', '', 'admin'); + + elgg_register_action('admin/menu/save', '', 'admin'); + + elgg_register_action('admin/delete_admin_notice', '', 'admin'); + + elgg_register_action('profile/fields/reset', '', 'admin'); + elgg_register_action('profile/fields/add', '', 'admin'); + elgg_register_action('profile/fields/edit', '', 'admin'); + elgg_register_action('profile/fields/delete', '', 'admin'); + elgg_register_action('profile/fields/reorder', '', 'admin'); + + elgg_register_simplecache_view('css/admin'); + elgg_register_simplecache_view('js/admin'); + $url = elgg_get_simplecache_url('js', 'admin'); + elgg_register_js('elgg.admin', $url); + elgg_register_js('jquery.jeditable', 'vendors/jquery/jquery.jeditable.mini.js'); + + // administer + // dashboard + elgg_register_menu_item('page', array( + 'name' => 'dashboard', + 'href' => 'admin/dashboard', + 'text' => elgg_echo('admin:dashboard'), + 'context' => 'admin', + 'priority' => 10, + 'section' => 'administer' + )); + // statistics + elgg_register_admin_menu_item('administer', 'statistics', null, 20); + elgg_register_admin_menu_item('administer', 'overview', 'statistics'); + elgg_register_admin_menu_item('administer', 'server', 'statistics'); + + // users + elgg_register_admin_menu_item('administer', 'users', null, 20); + elgg_register_admin_menu_item('administer', 'online', 'users', 10); + elgg_register_admin_menu_item('administer', 'admins', 'users', 20); + elgg_register_admin_menu_item('administer', 'newest', 'users', 30); + elgg_register_admin_menu_item('administer', 'add', 'users', 40); + + // configure + // plugins + elgg_register_menu_item('page', array( + 'name' => 'plugins', + 'href' => 'admin/plugins', + 'text' => elgg_echo('admin:plugins'), + 'context' => 'admin', + 'priority' => 75, + 'section' => 'configure' + )); + + // settings + elgg_register_admin_menu_item('configure', 'appearance', null, 50); + elgg_register_admin_menu_item('configure', 'settings', null, 100); + elgg_register_admin_menu_item('configure', 'basic', 'settings', 10); + elgg_register_admin_menu_item('configure', 'advanced', 'settings', 20); + elgg_register_admin_menu_item('configure', 'advanced/site_secret', 'settings', 25); + elgg_register_admin_menu_item('configure', 'menu_items', 'appearance', 30); + elgg_register_admin_menu_item('configure', 'profile_fields', 'appearance', 40); + // default widgets is added via an event handler elgg_default_widgets_init() in widgets.php + // because it requires additional setup. + + // plugin settings are added in elgg_admin_add_plugin_settings_menu() via the admin page handler + // for performance reasons. + + // we want plugin settings menu items to be sorted alphabetical + if (elgg_in_context('admin')) { + elgg_register_plugin_hook_handler('prepare', 'menu:page', 'elgg_admin_sort_page_menu'); + } + + if (elgg_is_admin_logged_in()) { + elgg_register_menu_item('topbar', array( + 'name' => 'administration', + 'href' => 'admin', + 'text' => elgg_view_icon('settings') . elgg_echo('admin'), + 'priority' => 100, + 'section' => 'alt', + )); + } + + // widgets + $widgets = array('online_users', 'new_users', 'content_stats', 'admin_welcome', 'control_panel'); + foreach ($widgets as $widget) { + elgg_register_widget_type( + $widget, + elgg_echo("admin:widget:$widget"), + elgg_echo("admin:widget:$widget:help"), + 'admin' + ); + } + + // automatic adding of widgets for admin + elgg_register_event_handler('make_admin', 'user', 'elgg_add_admin_widgets'); + + elgg_register_page_handler('admin', 'admin_page_handler'); + elgg_register_page_handler('admin_plugin_screenshot', 'admin_plugin_screenshot_page_handler'); + elgg_register_page_handler('admin_plugin_text_file', 'admin_markdown_page_handler'); } /** - * Add submenu items for admin page. + * Create the plugin settings page menu. * - * @return unknown_type + * This is done in a separate function called from the admin + * page handler because of performance concerns. + * + * @return void + * @access private + * @since 1.8.0 + */ +function elgg_admin_add_plugin_settings_menu() { + + $active_plugins = elgg_get_plugins('active'); + if (!$active_plugins) { + // nothing added because no items + return; + } + + foreach ($active_plugins as $plugin) { + $plugin_id = $plugin->getID(); + $settings_view_old = 'settings/' . $plugin_id . '/edit'; + $settings_view_new = 'plugins/' . $plugin_id . '/settings'; + if (elgg_view_exists($settings_view_new) || elgg_view_exists($settings_view_old)) { + elgg_register_menu_item('page', array( + 'name' => $plugin_id, + 'href' => "admin/plugin_settings/$plugin_id", + 'text' => $plugin->getManifest()->getName(), + 'parent_name' => 'settings', + 'context' => 'admin', + 'section' => 'configure', + )); + } + } +} + +/** + * Sort the plugin settings menu items + * + * @param string $hook + * @param string $type + * @param array $return + * @param array $params + * + * @return void + * @since 1.8.0 + * @access private + */ +function elgg_admin_sort_page_menu($hook, $type, $return, $params) { + $configure_items = $return['configure']; + /* @var ElggMenuItem[] $configure_items */ + foreach ($configure_items as $menu_item) { + if ($menu_item->getName() == 'settings') { + $settings = $menu_item; + } + } + + // keep the basic and advanced settings at the top + /* @var ElggMenuItem $settings */ + $children = $settings->getChildren(); + $site_settings = array_splice($children, 0, 2); + usort($children, array('ElggMenuBuilder', 'compareByText')); + array_splice($children, 0, 0, $site_settings); + $settings->setChildren($children); +} + +/** + * Handles any set up required for administration pages + * + * @return void + * @access private */ function admin_pagesetup() { - if (get_context() == 'admin') { - global $CONFIG; + if (elgg_in_context('admin')) { + $url = elgg_get_simplecache_url('css', 'admin'); + elgg_register_css('elgg.admin', $url); + elgg_load_css('elgg.admin'); + elgg_unregister_css('elgg'); + + // setup footer menu + elgg_register_menu_item('admin_footer', array( + 'name' => 'faq', + 'text' => elgg_echo('admin:footer:faq'), + 'href' => 'http://docs.elgg.org/wiki/Category:Administration_FAQ', + )); + + elgg_register_menu_item('admin_footer', array( + 'name' => 'manual', + 'text' => elgg_echo('admin:footer:manual'), + 'href' => 'http://docs.elgg.org/wiki/Administration_Manual', + )); - add_submenu_item(elgg_echo('admin:statistics'), $CONFIG->wwwroot . 'pg/admin/statistics/'); - add_submenu_item(elgg_echo('admin:site'), $CONFIG->wwwroot . 'pg/admin/site/'); - add_submenu_item(elgg_echo('admin:user'), $CONFIG->wwwroot . 'pg/admin/user/'); - add_submenu_item(elgg_echo('admin:plugins'), $CONFIG->wwwroot . 'pg/admin/plugins/'); + elgg_register_menu_item('admin_footer', array( + 'name' => 'community_forums', + 'text' => elgg_echo('admin:footer:community_forums'), + 'href' => 'http://community.elgg.org/groups/all/', + )); + + elgg_register_menu_item('admin_footer', array( + 'name' => 'blog', + 'text' => elgg_echo('admin:footer:blog'), + 'href' => 'http://blog.elgg.org/', + )); } } /** - * Handle admin pages. + * Handle admin pages. Expects corresponding views as admin/section/subsection + * + * @param array $page Array of pages * - * @param $page - * @return unknown_type + * @return bool + * @access private */ -function admin_settings_page_handler($page) { - global $CONFIG; +function admin_page_handler($page) { - $path = $CONFIG->path . "admin/index.php"; + admin_gatekeeper(); + elgg_admin_add_plugin_settings_menu(); + elgg_set_context('admin'); - if ($page[0]) { - switch ($page[0]) { - case 'user' : $path = $CONFIG->path . "admin/user.php"; break; - case 'statistics' : $path = $CONFIG->path . "admin/statistics.php"; break; - case 'plugins' : $path = $CONFIG->path . "admin/plugins.php"; break; - case 'site' : $path = $CONFIG->path . "admin/site.php"; break; + elgg_unregister_css('elgg'); + elgg_load_js('elgg.admin'); + elgg_load_js('jquery.jeditable'); + + // default to dashboard + if (!isset($page[0]) || empty($page[0])) { + $page = array('dashboard'); + } + + // was going to fix this in the page_handler() function but + // it's commented to explicitly return a string if there's a trailing / + if (empty($page[count($page) - 1])) { + array_pop($page); + } + + $vars = array('page' => $page); + + // special page for plugin settings since we create the form for them + if ($page[0] == 'plugin_settings') { + if (isset($page[1]) && (elgg_view_exists("settings/{$page[1]}/edit") || + elgg_view_exists("plugins/{$page[1]}/settings"))) { + + $view = 'admin/plugin_settings'; + $plugin = elgg_get_plugin_from_id($page[1]); + $vars['plugin'] = $plugin; + + $title = elgg_echo("admin:{$page[0]}"); + } else { + forward('', '404'); + } + } else { + $view = 'admin/' . implode('/', $page); + $title = elgg_echo("admin:{$page[0]}"); + if (count($page) > 1) { + $title .= ' : ' . elgg_echo('admin:' . implode(':', $page)); } } - if ($page[1]) { - set_input('username', $page[1]); + // gets content and prevents direct access to 'components' views + if ($page[0] == 'components' || !($content = elgg_view($view, $vars))) { + $title = elgg_echo('admin:unknown_section'); + $content = elgg_echo('admin:unknown_section'); } - include($path); + $body = elgg_view_layout('admin', array('content' => $content, 'title' => $title)); + echo elgg_view_page($title, $body, 'admin'); + return true; } /** - * Write a persistent message to the administrator's notification window. - * - * Currently this writes a message to the admin store, we may want to come up with another way at some point. + * Serves up screenshots for plugins from + * admin_plugin_screenshot/<plugin_id>/<size>/<ss_name>.<ext> * - * @param string $subject Subject of the message - * @param string $message Body of the message + * @param array $pages The pages array + * @return bool + * @access private */ -function send_admin_message($subject, $message) { - $subject = sanitise_string($subject); - $message = sanitise_string($message); +function admin_plugin_screenshot_page_handler($pages) { + // only admins can use this for security + admin_gatekeeper(); + + $plugin_id = elgg_extract(0, $pages); + // only thumbnail or full. + $size = elgg_extract(1, $pages, 'thumbnail'); - if (($subject) && ($message)) { - $admin_message = new ElggObject(); - $admin_message->subtype = 'admin_message'; - $admin_message->access_id = ACCESS_PUBLIC; - $admin_message->title = $subject; - $admin_message->description = $message; + // the rest of the string is the filename + $filename_parts = array_slice($pages, 2); + $filename = implode('/', $filename_parts); + $filename = sanitise_filepath($filename, false); - return $admin_message->save(); + $plugin = new ElggPlugin($plugin_id); + if (!$plugin) { + $file = elgg_get_root_path() . '_graphics/icons/default/medium.png'; + } else { + $file = $plugin->getPath() . $filename; + if (!file_exists($file)) { + $file = elgg_get_root_path() . '_graphics/icons/default/medium.png'; + } } - return false; + header("Content-type: image/jpeg"); + + // resize to 100x100 for thumbnails + switch ($size) { + case 'thumbnail': + echo get_resized_image_from_existing_file($file, 100, 100, true); + break; + + case 'full': + default: + echo file_get_contents($file); + break; + } + return true; } /** - * List all admin messages. + * Formats and serves out markdown files from plugins. * - * @param int $limit Limit + * URLs in format like admin_plugin_text_file/<plugin_id>/filename.ext + * + * The only valid files are: + * * README.txt + * * CHANGES.txt + * * INSTALL.txt + * * COPYRIGHT.txt + * * LICENSE.txt + * + * @param array $pages + * @return bool + * @access private */ -function list_admin_messages($limit = 10) { - return elgg_list_entities(array( - 'type' => 'object', - 'subtype' => 'admin_message', - 'limit' => $limit +function admin_markdown_page_handler($pages) { + admin_gatekeeper(); + + elgg_set_context('admin'); + + elgg_unregister_css('elgg'); + elgg_load_js('elgg.admin'); + elgg_load_js('jquery.jeditable'); + elgg_load_library('elgg:markdown'); + + $plugin_id = elgg_extract(0, $pages); + $plugin = elgg_get_plugin_from_id($plugin_id); + $filename = elgg_extract(1, $pages); + + $error = false; + if (!$plugin) { + $error = elgg_echo('admin:plugins:markdown:unknown_plugin'); + $body = elgg_view_layout('admin', array('content' => $error, 'title' => $error)); + echo elgg_view_page($error, $body, 'admin'); + return true; + } + + $text_files = $plugin->getAvailableTextFiles(); + + if (!array_key_exists($filename, $text_files)) { + $error = elgg_echo('admin:plugins:markdown:unknown_file'); + } + + $file = $text_files[$filename]; + $file_contents = file_get_contents($file); + + if (!$file_contents) { + $error = elgg_echo('admin:plugins:markdown:unknown_file'); + } + + if ($error) { + $title = $error; + $body = elgg_view_layout('admin', array('content' => $error, 'title' => $title)); + echo elgg_view_page($title, $body, 'admin'); + return true; + } + + $title = $plugin->getManifest()->getName() . ": $filename"; + $text = Markdown($file_contents); + + $body = elgg_view_layout('admin', array( + // setting classes here because there's no way to pass classes + // to the layout + 'content' => '<div class="elgg-markdown">' . $text . '</div>', + 'title' => $title )); + + echo elgg_view_page($title, $body, 'admin'); + return true; } /** - * Remove an admin message. + * Adds default admin widgets to the admin dashboard. + * + * @param string $event + * @param string $type + * @param ElggUser $user * - * @param int $guid The + * @return null|true + * @access private */ -function clear_admin_message($guid) { - return delete_entity($guid); +function elgg_add_admin_widgets($event, $type, $user) { + elgg_set_ignore_access(true); + + // check if the user already has widgets + if (elgg_get_widgets($user->getGUID(), 'admin')) { + return true; + } + + // In the form column => array of handlers in order, top to bottom + $adminWidgets = array( + 1 => array('control_panel', 'admin_welcome'), + 2 => array('online_users', 'new_users', 'content_stats'), + ); + + foreach ($adminWidgets as $column => $handlers) { + foreach ($handlers as $position => $handler) { + $guid = elgg_create_widget($user->getGUID(), $handler, 'admin'); + if ($guid) { + $widget = get_entity($guid); + /* @var ElggWidget $widget */ + $widget->move($column, $position); + } + } + } + elgg_set_ignore_access(false); } -/// Register init functions -register_elgg_event_handler('init', 'system', 'admin_init'); -register_elgg_event_handler('pagesetup', 'system', 'admin_pagesetup'); +elgg_register_event_handler('init', 'system', 'admin_init'); +elgg_register_event_handler('pagesetup', 'system', 'admin_pagesetup', 1000); diff --git a/engine/lib/annotations.php b/engine/lib/annotations.php index 666b83309..5e9b530de 100644 --- a/engine/lib/annotations.php +++ b/engine/lib/annotations.php @@ -5,130 +5,19 @@ * * @package Elgg * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ */ /** - * Include the ElggExtender superclass - * - */ -require_once('extender.php'); - -/** - * ElggAnnotation - * - * An annotation is similar to metadata each entity can contain more than one of each annotation. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - */ -class ElggAnnotation extends ElggExtender { - - /** - * Construct a new site object, optionally from a given id value or db row. - * - * @param mixed $id - */ - function __construct($id = null) { - $this->attributes = array(); - - if (!empty($id)) { - if ($id instanceof stdClass) { - $annotation = $id; - } else { - $annotation = get_annotation($id); - } - - if ($annotation) { - $objarray = (array) $annotation; - - foreach($objarray as $key => $value) { - $this->attributes[$key] = $value; - } - - $this->attributes['type'] = "annotation"; - } - } - } - - /** - * 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 void - */ - function __set($name, $value) { - return $this->set($name, $value); - } - - /** - * Save this instance - * - * @return int an object id - */ - function save() { - if ($this->id > 0) { - return update_annotation($this->id, $this->name, $this->value, $this->value_type, $this->owner_guid, $this->access_id); - } else { - $this->id = create_annotation($this->entity_guid, $this->name, $this->value, - $this->value_type, $this->owner_guid, $this->access_id); - - if (!$this->id) { - throw new IOException(sprintf(elgg_new('IOException:UnableToSaveNew'), get_class())); - } - return $this->id; - } - } - - /** - * Delete a given site. - */ - function delete() { - return delete_annotation($this->id); - } - - /** - * Get a url for this annotation. - * - * @return string - */ - public function getURL() { - return get_annotation_url($this->id); - } - - // SYSTEM LOG INTERFACE //////////////////////////////////////////////////////////// - - /** - * 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_annotation($id); - } -} - -/** * Convert a database row to a new ElggAnnotation * - * @param stdClass $row - * @return stdClass or ElggAnnotation + * @param stdClass $row Db row result object + * + * @return ElggAnnotation + * @access private */ function row_to_elggannotation($row) { if (!($row instanceof stdClass)) { + // @todo should throw in this case? return $row; } @@ -136,30 +25,46 @@ function row_to_elggannotation($row) { } /** - * Get a specific annotation. + * Get a specific annotation by its id. + * If you want multiple annotation objects, use + * {@link elgg_get_annotations()}. * - * @param int $annotation_id + * @param int $id The id of the annotation object being retrieved. + * + * @return ElggAnnotation|false */ -function get_annotation($annotation_id) { - global $CONFIG; - - $annotation_id = (int) $annotation_id; - $access = get_access_sql_suffix("a"); +function elgg_get_annotation_from_id($id) { + return elgg_get_metastring_based_object_from_id($id, 'annotations'); +} - return row_to_elggannotation(get_data_row("SELECT a.*, n.string as name, v.string as value from {$CONFIG->dbprefix}annotations a JOIN {$CONFIG->dbprefix}metastrings n on a.name_id = n.id JOIN {$CONFIG->dbprefix}metastrings v on a.value_id = v.id where a.id=$annotation_id and $access")); +/** + * Deletes an annotation using its ID. + * + * @param int $id The annotation ID to delete. + * @return bool + */ +function elgg_delete_annotation_by_id($id) { + $annotation = elgg_get_annotation_from_id($id); + if (!$annotation) { + return false; + } + return $annotation->delete(); } /** * Create a new annotation. * - * @param int $entity_guid - * @param string $name - * @param string $value - * @param string $value_type - * @param int $owner_guid - * @param int $access_id + * @param int $entity_guid Entity Guid + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value (default is auto detection) + * @param int $owner_guid Owner of annotation (default is logged in user) + * @param int $access_id Access level of annotation + * + * @return int|bool id on success or false on failure */ -function create_annotation($entity_guid, $name, $value, $value_type, $owner_guid, $access_id = ACCESS_PRIVATE) { +function create_annotation($entity_guid, $name, $value, $value_type = '', +$owner_guid = 0, $access_id = ACCESS_PRIVATE) { global $CONFIG; $result = false; @@ -170,8 +75,8 @@ function create_annotation($entity_guid, $name, $value, $value_type, $owner_guid $value_type = detect_extender_valuetype($value, sanitise_string(trim($value_type))); $owner_guid = (int)$owner_guid; - if ($owner_guid==0) { - $owner_guid = get_loggedin_userid(); + if ($owner_guid == 0) { + $owner_guid = elgg_get_logged_in_user_guid(); } $access_id = (int)$access_id; @@ -190,20 +95,20 @@ function create_annotation($entity_guid, $name, $value, $value_type, $owner_guid $entity = get_entity($entity_guid); - if (trigger_elgg_event('annotate',$entity->type,$entity)) { - system_log($entity, 'annotate'); - + if (elgg_trigger_event('annotate', $entity->type, $entity)) { // If ok then add it $result = insert_data("INSERT into {$CONFIG->dbprefix}annotations (entity_guid, name_id, value_id, value_type, owner_guid, time_created, access_id) VALUES ($entity_guid,'$name',$value,'$value_type', $owner_guid, $time, $access_id)"); - if ($result!==false) { - $obj = get_annotation($result); - if (trigger_elgg_event('create', 'annotation', $obj)) { + if ($result !== false) { + $obj = elgg_get_annotation_from_id($result); + if (elgg_trigger_event('create', 'annotation', $obj)) { return $result; } else { - delete_annotation($result); + // plugin returned false to reject annotation + elgg_delete_annotation_by_id($result); + return FALSE; } } } @@ -214,12 +119,14 @@ function create_annotation($entity_guid, $name, $value, $value_type, $owner_guid /** * Update an annotation. * - * @param int $annotation_id - * @param string $name - * @param string $value - * @param string $value_type - * @param int $owner_guid - * @param int $access_id + * @param int $annotation_id Annotation ID + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid Owner of annotation + * @param int $access_id Access level of annotation + * + * @return bool */ function update_annotation($annotation_id, $name, $value, $value_type, $owner_guid, $access_id) { global $CONFIG; @@ -230,8 +137,8 @@ function update_annotation($annotation_id, $name, $value, $value_type, $owner_gu $value_type = detect_extender_valuetype($value, sanitise_string(trim($value_type))); $owner_guid = (int)$owner_guid; - if ($owner_guid==0) { - $owner_guid = get_loggedin_userid(); + if ($owner_guid == 0) { + $owner_guid = elgg_get_logged_in_user_guid(); } $access_id = (int)$access_id; @@ -251,961 +158,333 @@ function update_annotation($annotation_id, $name, $value, $value_type, $owner_gu // If ok then add it $result = update_data("UPDATE {$CONFIG->dbprefix}annotations - set value_id='$value', value_type='$value_type', access_id=$access_id, owner_guid=$owner_guid - where id=$annotation_id and name_id='$name' and $access"); - - if ($result!==false) { - $obj = get_annotation($annotation_id); - if (trigger_elgg_event('update', 'annotation', $obj)) { - return true; - } else { - delete_annotation($annotation_id); - } + set name_id='$name', value_id='$value', value_type='$value_type', access_id=$access_id, owner_guid=$owner_guid + where id=$annotation_id and $access"); + + if ($result !== false) { + // @todo add plugin hook that sends old and new annotation information before db access + $obj = elgg_get_annotation_from_id($annotation_id); + elgg_trigger_event('update', 'annotation', $obj); } return $result; } /** - * Get a list of annotations for a given object/user/annotation type. + * Returns annotations. Accepts all elgg_get_entities() options for entity + * restraints. + * + * @see elgg_get_entities * - * @param int|array $entity_guid - * @param string $entity_type - * @param string $entity_subtype - * @param string $name - * @param mixed $value - * @param int|array $owner_guid - * @param int $limit - * @param int $offset - * @param string $order_by + * @param array $options Array in format: + * + * annotation_names => NULL|ARR Annotation names + * annotation_values => NULL|ARR Annotation values + * annotation_ids => NULL|ARR annotation ids + * annotation_case_sensitive => BOOL Overall Case sensitive + * annotation_owner_guids => NULL|ARR guids for annotation owners + * annotation_created_time_lower => INT Lower limit for created time. + * annotation_created_time_upper => INT Upper limit for created time. + * annotation_calculation => STR Perform the MySQL function on the annotation values returned. + * Do not confuse this "annotation_calculation" option with the + * "calculation" option to elgg_get_entities_from_annotation_calculation(). + * The "annotation_calculation" option causes this function to + * return the result of performing a mathematical calculation on + * all annotations that match the query instead of ElggAnnotation + * objects. + * See the docs for elgg_get_entities_from_annotation_calculation() + * for the proper use of the "calculation" option. + * + * + * @return ElggAnnotation[]|mixed + * @since 1.8.0 */ -function get_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", $name = "", -$value = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", $timelower = 0, $timeupper = 0, $entity_owner_guid = 0) { - global $CONFIG; - - $timelower = (int) $timelower; - $timeupper = (int) $timeupper; - - if (is_array($entity_guid)) { - if (sizeof($entity_guid) > 0) { - foreach($entity_guid as $key => $val) { - $entity_guid[$key] = (int) $val; - } - } else { - $entity_guid = 0; - } - } else { - $entity_guid = (int)$entity_guid; - } - $entity_type = sanitise_string($entity_type); - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - if ($name) { - $name = get_metastring_id($name); - - if ($name === false) { - $name = 0; - } - } - if ($value != "") { - $value = get_metastring_id($value); - } - - if (is_array($owner_guid)) { - if (sizeof($owner_guid) > 0) { - foreach($owner_guid as $key => $val) { - $owner_guid[$key] = (int) $val; - } - } else { - $owner_guid = 0; - } - } else { - $owner_guid = (int)$owner_guid; - } - - if (is_array($entity_owner_guid)) { - if (sizeof($entity_owner_guid) > 0) { - foreach($entity_owner_guid as $key => $val) { - $entity_owner_guid[$key] = (int) $val; - } - } else { - $entity_owner_guid = 0; - } - } else { - $entity_owner_guid = (int)$entity_owner_guid; - } - - $limit = (int)$limit; - $offset = (int)$offset; - if($order_by == 'asc') { - $order_by = "a.time_created asc"; - } - - if($order_by == 'desc') { - $order_by = "a.time_created desc"; - } - - $where = array(); +function elgg_get_annotations(array $options = array()) { - if ($entity_guid != 0 && !is_array($entity_guid)) { - $where[] = "a.entity_guid=$entity_guid"; - } else if (is_array($entity_guid)) { - $where[] = "a.entity_guid in (". implode(",",$entity_guid) . ")"; - } - - if ($entity_type != "") { - $where[] = "e.type='$entity_type'"; - } - - if ($entity_subtype != "") { - $where[] = "e.subtype='$entity_subtype'"; - } - - if ($owner_guid != 0 && !is_array($owner_guid)) { - $where[] = "a.owner_guid=$owner_guid"; + // @todo remove support for count shortcut - see #4393 + if (isset($options['__egefac']) && $options['__egefac']) { + unset($options['__egefac']); } else { - if (is_array($owner_guid)) { - $where[] = "a.owner_guid in (" . implode(",",$owner_guid) . ")"; - } - } - - if ($entity_owner_guid != 0 && !is_array($entity_owner_guid)) { - $where[] = "e.owner_guid=$entity_owner_guid"; - } else { - if (is_array($entity_owner_guid)) { - $where[] = "e.owner_guid in (" . implode(",",$entity_owner_guid) . ")"; - } - } - - if ($name !== "") { - $where[] = "a.name_id='$name'"; - } - - if ($value != "") { - $where[] = "a.value_id='$value'"; - } - - if ($timelower) { - $where[] = "a.time_created >= {$timelower}"; - } - - if ($timeupper) { - $where[] = "a.time_created <= {$timeupper}"; - } - - $query = "SELECT a.*, n.string as name, v.string as value - FROM {$CONFIG->dbprefix}annotations a - JOIN {$CONFIG->dbprefix}entities e on a.entity_guid = e.guid - JOIN {$CONFIG->dbprefix}metastrings v on a.value_id=v.id - JOIN {$CONFIG->dbprefix}metastrings n on a.name_id = n.id where "; - - foreach ($where as $w) { - $query .= " $w and "; - } - $query .= get_access_sql_suffix("a"); // Add access controls - $query .= " order by $order_by limit $offset,$limit"; // Add order and limit - - return get_data($query, "row_to_elggannotation"); + // support shortcut of 'count' => true for 'annotation_calculation' => 'count' + if (isset($options['count']) && $options['count']) { + $options['annotation_calculation'] = 'count'; + unset($options['count']); + } + } + + $options['metastring_type'] = 'annotations'; + return elgg_get_metastring_based_objects($options); } - /** + * Deletes annotations based on $options. * - * @todo Add support for arrays of names and values + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * This requires at least one constraint: annotation_owner_guid(s), + * annotation_name(s), annotation_value(s), or guid(s) must be set. * - * @param $options - * @return unknown_type + * @param array $options An options array. {@See elgg_get_annotations()} + * @return bool|null true on success, false on failure, null if no annotations to delete. + * @since 1.8.0 */ -function elgg_get_entities_from_annotations(array $options = array()) { - $defaults = array( - 'annotation_names' => NULL, - 'annotation_name' => NULL, - 'annotation_values' => NULL, - 'annotation_value' => NULL, - 'annotation_name_value_pair' => NULL, - 'annotation_name_value_pairs' => NULL, - 'annotation_name_value_pairs_operator' => 'AND', - 'annotation_case_sensitive' => TRUE, - 'order_by' => 'maxtime desc', - 'group_by' => 'a.entity_guid' - ); - - $options = array_merge($defaults, $options); - - $singulars = array('annotation_name', 'annotation_value', 'annotation_name_value_pair'); - $options = elgg_normalise_plural_options_array($options, $singulars); - - $clauses = elgg_get_entity_annotation_where_sql('e', $options['annotation_names'], $options['annotation_values'], - $options['annotation_name_value_pairs'], $options['annotation_name_value_pairs_operator'], $options['annotation_case_sensitive']); - - if ($clauses) { - // merge wheres to pass to get_entities() - if (isset($options['wheres']) && !is_array($options['wheres'])) { - $options['wheres'] = array($options['wheres']); - } elseif (!isset($options['wheres'])) { - $options['wheres'] = array(); - } - - $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']); - - // merge joins to pass to get_entities() - if (isset($options['joins']) && !is_array($options['joins'])) { - $options['joins'] = array($options['joins']); - } elseif (!isset($options['joins'])) { - $options['joins'] = array(); - } - - $options['joins'] = array_merge($options['joins'], $clauses['joins']); - - // merge selects to pass to get_entities() - if (isset($options['selects']) && !is_array($options['selects'])) { - $options['selects'] = array($options['selects']); - } elseif (!isset($options['selects'])) { - $options['selects'] = array(); - } - - $options['selects'] = array_merge($options['selects'], $clauses['selects']); - - /* @todo overwrites the current order and group bys - if ($clauses['order_by']) { - $options['order_by'] = $clauses['order_by']; - } - if ($clauses['group_by']) { - $options['group_by'] = $clauses['group_by']; - } - */ +function elgg_delete_annotations(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'annotations')) { + return false; } - return elgg_get_entities($options); + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); } /** - * Returns annotation name and value SQL where for entities. - * nb: $names and $values are not paired. Use $pairs for this. - * Pairs default to '=' operand. + * Disables annotations based on $options. * - * @param $prefix - * @param ARR|NULL $names - * @param ARR|NULL $values - * @param ARR|NULL $pairs array of names / values / operands - * @param AND|OR $pair_operator Operator to use to join the where clauses for pairs - * @param BOOL $case_sensitive - * @return FALSE|array False on fail, array('joins', 'wheres') - */ -function elgg_get_entity_annotation_where_sql($table, $names = NULL, $values = NULL, $pairs = NULL, $pair_operator = 'AND', $case_sensitive = TRUE) { - global $CONFIG; - - // short circuit if nothing requested - // 0 is a valid (if not ill-conceived) annotation name. - // 0 is also a valid annotation value for FALSE, NULL, or 0 - if ((!$names && $names !== 0) - && (!$values && $values !== 0) - && (!$pairs && $pairs !== 0)) { - return ''; - } - - // binary forces byte-to-byte comparision of strings, making - // it case- and diacritical-mark- sensitive. - // only supported on values. - $binary = ($case_sensitive) ? ' BINARY ' : ''; - - $access = get_access_sql_suffix('a'); - - $return = array ( - 'joins' => array (), - 'wheres' => array(), - 'selects' => array() - ); - - $wheres = array(); - - // get names wheres and joins - $names_where = ''; - if ($names !== NULL) { - $return['joins'][] = "JOIN {$CONFIG->dbprefix}annotations a on {$table}.guid = a.entity_guid"; - if (!is_array($names)) { - $names = array($names); - } - - $sanitised_names = array(); - foreach ($names as $name) { - // normalise to 0. - if (!$name) { - $name = '0'; - } - $sanitised_names[] = "'$name'"; - } - - if ($names_str = implode(',', $sanitised_names)) { - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msn on a.name_id = msn.id"; - $names_where = "(msn.string IN ($names_str))"; - } - } - - // get values wheres and joins - $values_where = ''; - if ($values !== NULL) { - $return['joins'][] = "JOIN {$CONFIG->dbprefix}annotations a on {$table}.guid = a.entity_guid"; - - if (!is_array($values)) { - $values = array($values); - } - - $sanitised_values = array(); - foreach ($values as $value) { - // normalize to 0 - if (!$value) { - $value = 0; - } - $sanitised_values[] = "'$value'"; - } - - if ($values_str = implode(',', $sanitised_values)) { - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msv on a.value_id = msv.id"; - $values_where = "({$binary}msv.string IN ($values_str))"; - } - } - - if ($names_where && $values_where) { - $wheres[] = "($names_where AND $values_where AND $access)"; - } elseif ($names_where) { - $wheres[] = "($names_where AND $access)"; - } elseif ($values_where) { - $wheres[] = "($values_where AND $access)"; - } - - // add pairs - // pairs must be in arrays. - if (is_array($pairs)) { - $array = array( - 'name' => 'test', - 'value' => 5 - ); - - $array = array('test' => 5); - - // check if this is an array of pairs or just a single pair. - if (isset($pairs['name']) || isset($pairs['value'])) { - $pairs = array($pairs); - } - - $pair_wheres = array(); - - // @todo when the pairs are > 3 should probably split the query up to - // denormalize the strings table. - $i = 1; - foreach ($pairs as $index => $pair) { - // @todo move this elsewhere? - // support shortcut 'n' => 'v' method. - if (!is_array($pair)) { - $pair = array( - 'name' => $index, - 'value' => $pair - ); - } - - // @todo The multiple joins are only needed when the operator is AND - $return['joins'][] = "JOIN {$CONFIG->dbprefix}annotations a{$i} on {$table}.guid = a{$i}.entity_guid"; - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msn{$i} on a{$i}.name_id = msn{$i}.id"; - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msv{$i} on a{$i}.value_id = msv{$i}.id"; - - // must have at least a name and value - if (!isset($pair['name']) || !isset($pair['value'])) { - // @todo should probably return false. - continue; - } - - // case sensitivity can be specified per pair. - // default to higher level setting. - if (isset($pair['case_sensitive'])) { - $pair_binary = ($pair['case_sensitive']) ? ' BINARY ' : ''; - } else { - $pair_binary = $binary; - } - - if (isset($pair['operand'])) { - $operand = mysql_real_escape_string($pair['operand']); - } else { - $operand = ' = '; - } - - // if the value is an int, don't quote it because str '15' < str '5' - // if the operand is IN don't quote it because quoting should be done already. - //$value = trim(strtolower($operand)) == 'in' ? $pair['value'] : "'{$pair['value']}'"; - if (trim(strtolower($operand)) == 'in' || sanitise_int($pair['value'])) { - $value = $pair['value']; - } else { - $value = "'{$pair['value']}'"; - } - - $access = get_access_sql_suffix("a{$i}"); - $pair_wheres[] = "(msn{$i}.string = '{$pair['name']}' AND {$pair_binary}msv{$i}.string $operand $value AND $access)"; - $i++; - } - - if ($where = implode (" $pair_operator ", $pair_wheres)) { - $wheres[] = "($where)"; - } - } - - if ($where = implode(' OR ', $wheres)) { - $return['selects'][] = "max(a.time_created) as maxtime"; - $return['wheres'][] = "($where)"; - $return['group_by'] = 'a.entity_guid'; - $return['order_by'] = 'maxtime asc'; - } - - return $return; -} - -/** - * Return a list of entities which are annotated with a specific annotation. - * These can be ordered by when the annotation was created/updated. + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! * - * @param string $entity_type Type of entity. - * @param string $entity_subtype Subtype of entity. - * @param string $name Name of annotation. - * @param string $value Value of annotation. - * @param int $owner_guid Owner. - * @param int $group_guid Group container. Currently this is only supported if $entity_type == 'object' - * @param int $limit Maximum number of results to return. - * @param int $offset Place to start. - * @param string $order_by How to order results. - * @param boolean $count Whether to count entities rather than return them - * @param int $timelower The earliest time the annotation can have been created. Default: all - * @param int $timeupper The latest time the annotation can have been created. Default: all + * @param array $options An options array. {@See elgg_get_annotations()} + * @return bool|null true on success, false on failure, null if no annotations disabled. + * @since 1.8.0 */ - - -/** - * @deprecated 1.7 Use elgg_get_entities_from_annotations() - * @param $entity_type - * @param $entity_subtype - * @param $name - * @param $value - * @param $owner_guid - * @param $group_guid - * @param $limit - * @param $offset - * @param $order_by - * @param $count - * @param $timelower - * @param $timeupper - * @return unknown_type - */ -function get_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", $value = "", -$owner_guid = 0, $group_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", $count = false, -$timelower = 0, $timeupper = 0) { - elgg_deprecated_notice('get_entities_from_annotations() was deprecated by elgg_get_entities_from_annotations().', 1.7); - - $options = array(); - - $options['annotation_names'] = $name; - - if ($value) { - $options['annotation_values'] = $value; - } - - if ($entity_type) { - $options['types'] = $entity_type; - } - - if ($entity_subtype) { - $options['subtypes'] = $entity_subtype; - } - - if ($owner_guid) { - $options['owner_guid'] = $owner_guid; - } - - if ($group_guid) { - $options['container_guid'] = $group_guid; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } - - if ($order_by) { - $options['order_by'] = "maxtime $order_by"; - } - - if ($count) { - $options['count'] = $count; +function elgg_disable_annotations(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'annotations')) { + return false; } + + // if we can see hidden (disabled) we need to use the offset + // otherwise we risk an infinite loop if there are more than 50 + $inc_offset = access_get_show_hidden_status(); - return elgg_get_entities_from_annotations($options); + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset); } /** - * Lists entities + * Enables annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! * - * @see elgg_view_entity_list + * @warning In order to enable annotations, you must first use + * {@link access_show_hidden_entities()}. * - * @param string $entity_type Type of entity. - * @param string $entity_subtype Subtype of entity. - * @param string $name Name of annotation. - * @param string $value Value of annotation. - * @param int $limit Maximum number of results to return. - * @param int $owner_guid Owner. - * @param int $group_guid Group container. Currently this is only supported if $entity_type == 'object' - * @param boolean $asc Whether to list in ascending or descending order (default: desc) - * @param boolean $fullview Whether to display the entities in full - * @param boolean $viewtypetoggle Determines whether or not the 'gallery' view can be displayed (default: no) - * @return string Formatted entity list + * @param array $options An options array. {@See elgg_get_annotations()} + * @return bool|null true on success, false on failure, null if no metadata enabled. + * @since 1.8.0 */ -function list_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", $value = "", $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, $viewtypetoggle = false) { - if ($asc) { - $asc = "asc"; - } else { - $asc = "desc"; +function elgg_enable_annotations(array $options) { + if (!$options || !is_array($options)) { + return false; } - $count = get_entities_from_annotations($entity_type, $entity_subtype, $name, - $value, $owner_guid, $group_guid, null, null, $asc, true); - $offset = (int) get_input("offset",0); - $entities = get_entities_from_annotations($entity_type, $entity_subtype, $name, - $value, $owner_guid, $group_guid, $limit, $offset, $asc); - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle); + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback'); } /** - * Returns a human-readable list of annotations on a particular entity. + * Returns a rendered list of annotations with pagination. + * + * @param array $options Annotation getter and display options. + * {@see elgg_get_annotations()} and {@see elgg_list_entities()}. * - * @param int $entity_guid The entity GUID - * @param string $name The name of the kind of annotation - * @param int $limit The number of annotations to display at once - * @param true|false $asc Whether or not the annotations are displayed in ascending order. (Default: true) - * @return string HTML (etc) version of the annotation list + * @return string The list of entities + * @since 1.8.0 */ -function list_annotations($entity_guid, $name = "", $limit = 25, $asc = true) { - if ($asc) { - $asc = "asc"; - } else { - $asc = "desc"; - } - $count = count_annotations($entity_guid, "", "", $name); - $offset = (int) get_input("annoff",0); - $annotations = get_annotations($entity_guid, "", "", $name, "", "", $limit, $offset, $asc); +function elgg_list_annotations($options) { + $defaults = array( + 'limit' => 25, + 'offset' => (int) max(get_input('annoff', 0), 0), + ); - return elgg_view_annotation_list($annotations, $count, $offset, $limit); -} + $options = array_merge($defaults, $options); -/** - * Return the sum of a given integer annotation. - * - * @param $entity_guid int - * @param $entity_type string - * @param $entity_subtype string - * @param $name string - */ -function get_annotations_sum($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0) { - return __get_annotations_calculate_x("sum", $entity_guid, $entity_type, $entity_subtype, $name, $value, $value_type, $owner_guid); + return elgg_list_entities($options, 'elgg_get_annotations', 'elgg_view_annotation_list'); } /** - * Return the max of a given integer annotation. - * - * @param $entity_guid int - * @param $entity_type string - * @param $entity_subtype string - * @param $name string + * Entities interfaces */ -function get_annotations_max($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0) { - return __get_annotations_calculate_x("max", $entity_guid, $entity_type, $entity_subtype, $name, $value, $value_type, $owner_guid); -} /** - * Return the minumum of a given integer annotation. + * Returns entities based upon annotations. Also accepts all options available + * to elgg_get_entities() and elgg_get_entities_from_metadata(). * - * @param $entity_guid int - * @param $entity_type string - * @param $entity_subtype string - * @param $name string - */ -function get_annotations_min($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0) { - return __get_annotations_calculate_x("min", $entity_guid, $entity_type, $entity_subtype, $name, $value, $value_type, $owner_guid); -} - -/** - * Return the average of a given integer annotation. + * Entity creation time is selected as maxtime. To sort based upon + * this, pass 'order_by' => 'maxtime asc' || 'maxtime desc' * - * @param $entity_guid int - * @param $entity_type string - * @param $entity_subtype string - * @param $name string - */ -function get_annotations_avg($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0) { - return __get_annotations_calculate_x("avg", $entity_guid, $entity_type, $entity_subtype, $name, $value, $value_type, $owner_guid); -} - -/** - * Count the number of annotations based on search parameters + * @see elgg_get_entities + * @see elgg_get_entities_from_metadata * - * @param int $entity_guid - * @param string $entity_type - * @param string $entity_subtype - * @param string $name - */ -function count_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, $timeupper = 0) { - return __get_annotations_calculate_x("count", $entity_guid, $entity_type, $entity_subtype, $name, $value, $value_type, $owner_guid, $timelower, $timeupper); -} - -/** - * Perform a mathmatical calculation on integer annotations. + * @param array $options Array in format: * - * @param $sum string - * @param $entity_id int - * @param $entity_type string - * @param $entity_subtype string - * @param $name string - */ -function __get_annotations_calculate_x($sum = "avg", $entity_guid, $entity_type = "", $entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, $timeupper = 0) { - global $CONFIG; - - $sum = sanitise_string($sum); - $entity_guid = (int)$entity_guid; - $entity_type = sanitise_string($entity_type); - $timeupper = (int)$timeupper; - $timelower = (int)$timelower; - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - if ($name != '' AND !$name = get_metastring_id($name)) { - return 0; - } - - if ($value != '' AND !$value = get_metastring_id($value)) { - return 0; - } - $value_type = sanitise_string($value_type); - $owner_guid = (int)$owner_guid; - - // if (empty($name)) return 0; - - $where = array(); - - if ($entity_guid) { - $where[] = "e.guid=$entity_guid"; - } - - if ($entity_type!="") { - $where[] = "e.type='$entity_type'"; - } - - if ($entity_subtype) { - $where[] = "e.subtype=$entity_subtype"; - } - - if ($name!="") { - $where[] = "a.name_id='$name'"; - } - - if ($value!="") { - $where[] = "a.value_id='$value'"; - } - - if ($value_type!="") { - $where[] = "a.value_type='$value_type'"; - } - - if ($owner_guid) { - $where[] = "a.owner_guid='$owner_guid'"; - } - - if ($timelower) { - $where[] = "a.time_created >= {$timelower}"; - } - - if ($timeupper) { - $where[] = "a.time_created <= {$timeupper}"; - } - - if ($sum != "count") { - $where[] = "a.value_type='integer'"; // Limit on integer types - } - - $query = "SELECT $sum(ms.string) as sum - FROM {$CONFIG->dbprefix}annotations a - JOIN {$CONFIG->dbprefix}entities e on a.entity_guid = e.guid - JOIN {$CONFIG->dbprefix}metastrings ms on a.value_id=ms.id WHERE "; - - foreach ($where as $w) { - $query .= " $w and "; - } - - $query .= get_access_sql_suffix("a"); // now add access - $query .= ' and ' . get_access_sql_suffix("e"); // now add access - - $row = get_data_row($query); - if ($row) { - return $row->sum; - } - - return false; -} - -/** - * Get entities ordered by a mathematical calculation + * annotation_names => NULL|ARR annotations names + * + * annotation_values => NULL|ARR annotations values * - * @param $sum string - * @param $entity_type string - * @param $entity_subtype string - * @param $name string - * @param $mdname string - * @param $mdvalue string - * @param $limit int - * @param string $orderdir Default: asc - the sort order - * @return unknown + * annotation_name_value_pairs => NULL|ARR (name = 'name', value => 'value', + * 'operator' => '=', 'case_sensitive' => TRUE) entries. + * Currently if multiple values are sent via an array (value => array('value1', 'value2') + * the pair's operator will be forced to "IN". + * + * annotation_name_value_pairs_operator => NULL|STR The operator to use for combining + * (name = value) OPERATOR (name = value); default AND + * + * annotation_case_sensitive => BOOL Overall Case sensitive + * + * order_by_annotation => NULL|ARR (array('name' => 'annotation_text1', 'direction' => ASC|DESC, + * 'as' => text|integer), + * + * Also supports array('name' => 'annotation_text1') + * + * annotation_owner_guids => NULL|ARR guids for annotaiton owners + * + * @return mixed If count, int. If not count, array. false on errors. + * @since 1.7.0 */ -function __get_entities_from_annotations_calculate_x($sum = "sum", $entity_type = "", $entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $owner_guid = 0, $limit = 10, $offset = 0, $orderdir = 'desc', $count = false) { - global $CONFIG; - - $sum = sanitise_string($sum); - $entity_type = sanitise_string($entity_type); - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - $name = get_metastring_id($name); - $limit = (int) $limit; - $offset = (int) $offset; - $owner_guid = (int) $owner_guid; - if (!empty($mdname) && !empty($mdvalue)) { - $meta_n = get_metastring_id($mdname); - $meta_v = get_metastring_id($mdvalue); - } - - if (empty($name)) { - return 0; - } - - $where = array(); +function elgg_get_entities_from_annotations(array $options = array()) { + $defaults = array( + 'annotation_names' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_values' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, - if ($entity_type!="") { - $where[] = "e.type='$entity_type'"; - } + 'annotation_name_value_pairs_operator' => 'AND', + 'annotation_case_sensitive' => TRUE, + 'order_by_annotation' => array(), - if ($owner_guid > 0) { - $where[] = "e.container_guid = $owner_guid"; - } + 'annotation_created_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_created_time_upper' => ELGG_ENTITIES_ANY_VALUE, - if ($entity_subtype) { - $where[] = "e.subtype=$entity_subtype"; - } + 'annotation_owner_guids' => ELGG_ENTITIES_ANY_VALUE, - if ($name!="") { - $where[] = "a.name_id='$name'"; - } + 'order_by' => 'maxtime desc', + 'group_by' => 'a.entity_guid' + ); - if (!empty($mdname) && !empty($mdvalue)) { - if ($mdname!="") { - $where[] = "m.name_id='$meta_n'"; - } + $options = array_merge($defaults, $options); - if ($mdvalue!="") { - $where[] = "m.value_id='$meta_v'"; - } - } + $singulars = array('annotation_name', 'annotation_value', + 'annotation_name_value_pair', 'annotation_owner_guid'); - if ($sum != "count") { - // Limit on integer types - $where[] = "a.value_type='integer'"; - } + $options = elgg_normalise_plural_options_array($options, $singulars); + $options = elgg_entities_get_metastrings_options('annotation', $options); - if (!$count) { - $query = "SELECT distinct e.*, $sum(ms.string) as sum "; - } else { - $query = "SELECT count(distinct e.guid) as num, $sum(ms.string) as sum "; + if (!$options) { + return false; } - $query .= " from {$CONFIG->dbprefix}entities e JOIN {$CONFIG->dbprefix}annotations a on a.entity_guid = e.guid JOIN {$CONFIG->dbprefix}metastrings ms on a.value_id=ms.id "; - if (!empty($mdname) && !empty($mdvalue)) { - $query .= " JOIN {$CONFIG->dbprefix}metadata m on m.entity_guid = e.guid "; - } + // special sorting for annotations + //@todo overrides other sorting + $options['selects'][] = "max(n_table.time_created) as maxtime"; + $options['group_by'] = 'n_table.entity_guid'; - $query .= " WHERE "; - foreach ($where as $w) { - $query .= " $w and "; - } + $time_wheres = elgg_get_entity_time_where_sql('a', $options['annotation_created_time_upper'], + $options['annotation_created_time_lower']); - $query .= get_access_sql_suffix("a"); // now add access - $query .= ' and ' . get_access_sql_suffix("e"); // now add access - if (!$count) { - $query .= ' group by e.guid'; + if ($time_wheres) { + $options['wheres'] = array_merge($options['wheres'], $time_wheres); } - if (!$count) { - $query .= ' order by sum ' . $orderdir; - $query .= ' limit ' . $offset . ' , ' . $limit; - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($row = get_data_row($query)) { - return $row->num; - } - } - return false; + return elgg_get_entities_from_metadata($options); } /** - * Returns entities ordered by the sum of an annotation + * Returns a viewable list of entities from annotations. * - * @param unknown_type $entity_type - * @param unknown_type $entity_subtype - * @param unknown_type $name - * @param string $mdname - * @param string $mdvalue - * @param unknown_type $owner_guid - * @param int $limit - * @param int $offset - * @param true|false $count - * @return unknown - */ -function get_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $owner_guid = 0, $limit = 10, $offset = 0, $orderdir = 'desc', $count = false) { - return __get_entities_from_annotations_calculate_x('sum',$entity_type,$entity_subtype,$name,$mdname, $mdvalue, $owner_guid,$limit, $offset, $orderdir, $count); -} - -/** - * Lists entities by the totals of a particular kind of annotation + * @param array $options Options array * - * @param string $entity_type Type of entity. - * @param string $entity_subtype Subtype of entity. - * @param string $name Name of annotation. - * @param int $limit Maximum number of results to return. - * @param int $owner_guid Owner. - * @param int $group_guid Group container. Currently this is only supported if $entity_type == 'object' - * @param boolean $asc Whether to list in ascending or descending order (default: desc) - * @param boolean $fullview Whether to display the entities in full - * @param boolean $viewtypetoggle Determines whether or not the 'gallery' view can be displayed (default: no) - * @return string Formatted entity list - */ -function list_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, $viewtypetoggle = false, $pagination = true, $orderdir = 'desc') { - if ($asc) { - $asc = "asc"; - } else { - $asc = "desc"; - } - - $offset = (int) get_input("offset",0); - $count = get_entities_from_annotation_count($entity_type, $entity_subtype, $name, '', '', $owner_guid, $limit, $offset, $orderdir, true); - $entities = get_entities_from_annotation_count($entity_type, $entity_subtype, $name, '', '', $owner_guid, $limit, $offset, $orderdir, false); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); -} - -/** - * Lists entities by the totals of a particular kind of annotation AND the value of a piece of metadata + * @see elgg_get_entities_from_annotations() + * @see elgg_list_entities() * - * @param string $entity_type Type of entity. - * @param string $entity_subtype Subtype of entity. - * @param string $name Name of annotation. - * @param string $mdname Metadata name - * @param string $mdvalue Metadata value - * @param int $limit Maximum number of results to return. - * @param int $owner_guid Owner. - * @param int $group_guid Group container. Currently this is only supported if $entity_type == 'object' - * @param boolean $asc Whether to list in ascending or descending order (default: desc) - * @param boolean $fullview Whether to display the entities in full - * @param boolean $viewtypetoggle Determines whether or not the 'gallery' view can be displayed (default: no) - * @return string Formatted entity list + * @return string */ -function list_entities_from_annotation_count_by_metadata($entity_type = "", $entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, $viewtypetoggle = false, $pagination = true, $orderdir = 'desc') { - if ($asc) { - $asc = "asc"; - } else { - $asc = "desc"; - } - - $offset = (int) get_input("offset",0); - $count = get_entities_from_annotation_count($entity_type, $entity_subtype, $name, $mdname, $mdvalue, $owner_guid, $limit, $offset, $orderdir, true); - $entities = get_entities_from_annotation_count($entity_type, $entity_subtype, $name, $mdname, $mdvalue, $owner_guid, $limit, $offset, $orderdir, false); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); +function elgg_list_entities_from_annotations($options = array()) { + return elgg_list_entities($options, 'elgg_get_entities_from_annotations'); } /** - * Delete a given annotation. + * Get entities ordered by a mathematical calculation on annotation values + * + * @param array $options An options array: + * 'calculation' => The calculation to use. Must be a valid MySQL function. + * Defaults to sum. Result selected as 'annotation_calculation'. + * Don't confuse this "calculation" option with the + * "annotation_calculation" option to elgg_get_annotations(). + * This "calculation" option is applied to each entity's set of + * annotations and is selected as annotation_calculation for that row. + * See the docs for elgg_get_annotations() for proper use of the + * "annotation_calculation" option. + * 'order_by' => The order for the sorting. Defaults to 'annotation_calculation desc'. + * 'annotation_names' => The names of annotations on the entity. + * 'annotation_values' => The values of annotations on the entity. * - * @param $id int The id + * 'metadata_names' => The name of metadata on the entity. + * 'metadata_values' => The value of metadata on the entitiy. + * + * @return mixed If count, int. If not count, array. false on errors. */ -function delete_annotation($id) { - global $CONFIG; +function elgg_get_entities_from_annotation_calculation($options) { + $db_prefix = elgg_get_config('dbprefix'); + $defaults = array( + 'calculation' => 'sum', + 'order_by' => 'annotation_calculation desc' + ); - $id = (int)$id; + $options = array_merge($defaults, $options); - $access = get_access_sql_suffix(); - $annotation = get_annotation($id); + $function = sanitize_string(elgg_extract('calculation', $options, 'sum', false)); - if (trigger_elgg_event('delete', 'annotation', $annotation)) { - remove_from_river_by_annotation($id); - return delete_data("DELETE from {$CONFIG->dbprefix}annotations where id=$id and $access"); - } + // you must cast this as an int or it sorts wrong. + $options['selects'][] = 'e.*'; + $options['selects'][] = "$function(cast(a_msv.string as signed)) as annotation_calculation"; - return FALSE; -} + // need our own join to get the values because the lower level functions don't + // add all the joins if it's a different callback. + $options['joins'][] = "JOIN {$db_prefix}metastrings a_msv ON n_table.value_id = a_msv.id"; -/** - * Clear all the annotations for a given entity, assuming you have access to that metadata. - * - * @param int $guid - */ -function clear_annotations($guid, $name = "") { - global $CONFIG; - - $guid = (int)$guid; - - if (!empty($name)) { - $name = get_metastring_id($name); - } - - $entity_guid = (int) $guid; - if ($entity = get_entity($entity_guid)) { - if ($entity->canEdit()) { - $where = array(); + // don't need access control because it's taken care of by elgg_get_annotations. + $options['group_by'] = 'n_table.entity_guid'; - if ($name != "") { - $where[] = " name_id='$name'"; - } + $options['callback'] = 'entity_row_to_elggstar'; - $query = "DELETE from {$CONFIG->dbprefix}annotations where entity_guid=$guid "; - foreach ($where as $w) { - $query .= " and $w"; - } + // see #4393 + // @todo remove after the 'count' shortcut is removed from elgg_get_annotations() + $options['__egefac'] = true; - return delete_data($query); - } - } + return elgg_get_annotations($options); } /** - * Clear all annotations belonging to a given owner_guid + * List entities from an annotation calculation. * - * @param int $owner_guid The owner + * @see elgg_get_entities_from_annotation_calculation() + * + * @param array $options An options array. + * + * @return string */ -function clear_annotations_by_owner($owner_guid) { - global $CONFIG; - - $owner_guid = (int)$owner_guid; - - $annotations = get_data("SELECT id from {$CONFIG->dbprefix}annotations WHERE owner_guid=$owner_guid"); - $deleted = 0; - - if (!$annotations) { - return 0; - } - - foreach ($annotations as $id) { - // Is this the best way? - if (delete_annotation($id->id)) { - $deleted++; - } - } +function elgg_list_entities_from_annotation_calculation($options) { + $defaults = array( + 'calculation' => 'sum', + 'order_by' => 'annotation_calculation desc' + ); + $options = array_merge($defaults, $options); - return $deleted; + return elgg_list_entities($options, 'elgg_get_entities_from_annotation_calculation'); } /** - * Handler called by trigger_plugin_hook on the "export" event. + * Export the annotations for the specified entity + * + * @param string $hook 'export' + * @param string $type 'all' + * @param mixed $returnvalue Default return value + * @param mixed $params Parameters determining what annotations to export + * + * @elgg_plugin_hook export all + * + * @return array + * @throws InvalidParameterException + * @access private */ -function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $params) { +function export_annotation_plugin_hook($hook, $type, $returnvalue, $params) { // Sanity check values if ((!is_array($params)) && (!isset($params['guid']))) { throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); @@ -1216,9 +495,12 @@ function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $param } $guid = (int)$params['guid']; - $name = $params['name']; + $options = array('guid' => $guid, 'limit' => 0); + if (isset($params['name'])) { + $options['annotation_name'] = $params['name']; + } - $result = get_annotations($guid); + $result = elgg_get_annotations($options); if ($result) { foreach ($result as $r) { @@ -1230,29 +512,107 @@ function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $param } /** - * Get the URL for this item of metadata, by default this links to the export handler in the current view. + * Get the URL for this item of metadata, by default this links to the + * export handler in the current view. + * + * @param int $id Annotation id * - * @param int $id + * @return mixed */ function get_annotation_url($id) { $id = (int)$id; - if ($extender = get_annotation($id)) { + if ($extender = elgg_get_annotation_from_id($id)) { return get_extender_url($extender); } return false; } +/** + * Check to see if a user has already created an annotation on an object + * + * @param int $entity_guid Entity guid + * @param string $annotation_type Type of annotation + * @param int $owner_guid Defaults to logged in user. + * + * @return bool + * @since 1.8.0 + */ +function elgg_annotation_exists($entity_guid, $annotation_type, $owner_guid = NULL) { + global $CONFIG; + + if (!$owner_guid && !($owner_guid = elgg_get_logged_in_user_guid())) { + return FALSE; + } + + $entity_guid = sanitize_int($entity_guid); + $owner_guid = sanitize_int($owner_guid); + $annotation_type = sanitize_string($annotation_type); + + $sql = "SELECT a.id FROM {$CONFIG->dbprefix}annotations a" . + " JOIN {$CONFIG->dbprefix}metastrings m ON a.name_id = m.id" . + " WHERE a.owner_guid = $owner_guid AND a.entity_guid = $entity_guid" . + " AND m.string = '$annotation_type'"; + + if (get_data_row($sql)) { + return TRUE; + } + + return FALSE; +} + +/** + * Return the URL for a comment + * + * @param ElggAnnotation $comment The comment object + * @return string + * @access private + */ +function elgg_comment_url_handler(ElggAnnotation $comment) { + $entity = $comment->getEntity(); + if ($entity) { + return $entity->getURL() . '#item-annotation-' . $comment->id; + } + return ""; +} /** * Register an annotation url handler. * - * @param string $function_name The function. * @param string $extender_name The name, default 'all'. + * @param string $function_name The function. + * + * @return string + */ +function elgg_register_annotation_url_handler($extender_name = "all", $function_name) { + return elgg_register_extender_url_handler('annotation', $extender_name, $function_name); +} + +/** + * Register annotation unit tests + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array + * @access private */ -function register_annotation_url_handler($function_name, $extender_name = "all") { - return register_extender_url_handler($function_name, 'annotation', $extender_name); +function annotations_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/annotations.php'; + return $value; +} + +/** + * Initialize the annotation library + * @access private + */ +function elgg_annotations_init() { + elgg_register_annotation_url_handler('generic_comment', 'elgg_comment_url_handler'); + + elgg_register_plugin_hook_handler("export", "all", "export_annotation_plugin_hook", 2); + elgg_register_plugin_hook_handler('unit_test', 'system', 'annotations_test'); } -/** Register the hook */ -register_plugin_hook("export", "all", "export_annotation_plugin_hook", 2); +elgg_register_event_handler('init', 'system', 'elgg_annotations_init'); diff --git a/engine/lib/cache.php b/engine/lib/cache.php index df7b1e525..3116c1a9b 100644 --- a/engine/lib/cache.php +++ b/engine/lib/cache.php @@ -3,435 +3,451 @@ * Elgg cache * Cache file interface for caching data. * - * @package Elgg - * @subpackage API - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Cache */ +/* Filepath Cache */ + /** - * ElggCache The elgg cache superclass. - * This defines the interface for a cache (wherever that cache is stored). + * Returns an ElggCache object suitable for caching system information + * + * @todo Can this be done in a cleaner way? + * @todo Swap to memcache etc? * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage API + * @return ElggFileCache */ -abstract class ElggCache implements - // Override for array access - ArrayAccess { - /** - * Variables for the cache object. - * - * @var array - */ - private $variables; +function elgg_get_system_cache() { + global $CONFIG; /** - * Set the constructor. + * A default filestore cache using the dataroot. */ - function __construct() { - $this->variables = array(); + static $FILE_PATH_CACHE; + + if (!$FILE_PATH_CACHE) { + $FILE_PATH_CACHE = new ElggFileCache($CONFIG->dataroot . 'system_cache/'); } - /** - * Set a cache variable. - * - * @param string $variable - * @param string $value - */ - public function set_variable($variable, $value) { - if (!is_array($this->variables)) { - $this->variables = array(); - } + return $FILE_PATH_CACHE; +} - $this->variables[$variable] = $value; - } +/** + * Reset the system cache by deleting the caches + * + * @return void + */ +function elgg_reset_system_cache() { + $cache = elgg_get_system_cache(); + $cache->clear(); +} - /** - * Get variables for this cache. - * - * @param string $variable - * @return mixed The variable or null; - */ - public function get_variable($variable) { - if (isset($this->variables[$variable])) { - return $this->variables[$variable]; - } +/** + * Saves a system cache. + * + * @param string $type The type or identifier of the cache + * @param string $data The data to be saved + * @return bool + */ +function elgg_save_system_cache($type, $data) { + global $CONFIG; - return null; + if ($CONFIG->system_cache_enabled) { + $cache = elgg_get_system_cache(); + return $cache->save($type, $data); } - /** - * Class member get overloading, returning key using $this->load defaults. - * - * @param string $key - * @return mixed - */ - function __get($key) { - return $this->load($key); - } + return false; +} - /** - * Class member set overloading, setting a key using $this->save defaults. - * - * @param string $key - * @param mixed $value - * @return mixed - */ - function __set($key, $value) { - return $this->save($key, $value); - } +/** + * Retrieve the contents of a system cache. + * + * @param string $type The type of cache to load + * @return string + */ +function elgg_load_system_cache($type) { + global $CONFIG; - /** - * Supporting isset, using $this->load() with default values. - * - * @param string $key The name of the attribute or metadata. - * @return bool - */ - function __isset($key) { - return (bool)$this->load($key); - } + if ($CONFIG->system_cache_enabled) { + $cache = elgg_get_system_cache(); + $cached_data = $cache->load($type); - /** - * Supporting unsetting of magic attributes. - * - * @param string $key The name of the attribute or metadata. - */ - function __unset($key) { - return $this->delete($key); + if ($cached_data) { + return $cached_data; + } } - /** - * Save data in a cache. - * - * @param string $key - * @param string $data - * @return bool - */ - abstract public function save($key, $data); + return NULL; +} - /** - * Load data from the cache using a given key. - * - * @param string $key - * @param int $offset - * @param int $limit - * @return mixed The stored data or false. - */ - abstract public function load($key, $offset = 0, $limit = null); +/** + * Enables the system disk cache. + * + * Uses the 'system_cache_enabled' datalist with a boolean value. + * Resets the system cache. + * + * @return void + */ +function elgg_enable_system_cache() { + global $CONFIG; - /** - * Invalidate a key - * - * @param string $key - * @return bool - */ - abstract public function delete($key); + datalist_set('system_cache_enabled', 1); + $CONFIG->system_cache_enabled = 1; + elgg_reset_system_cache(); +} - /** - * Clear out all the contents of the cache. - * - */ - abstract public function clear(); +/** + * Disables the system disk cache. + * + * Uses the 'system_cache_enabled' datalist with a boolean value. + * Resets the system cache. + * + * @return void + */ +function elgg_disable_system_cache() { + global $CONFIG; - /** - * Add a key only if it doesn't already exist. - * Implemented simply here, if you extend this class and your caching engine provides a better way then - * override this accordingly. - * - * @param string $key - * @param string $data - * @return bool - */ - public function add($key, $data) { - if (!isset($this[$key])) { - return $this->save($key, $data); - } + datalist_set('system_cache_enabled', 0); + $CONFIG->system_cache_enabled = 0; + elgg_reset_system_cache(); +} - return false; - } +/** @todo deprecate in Elgg 1.9 **/ - // ARRAY ACCESS INTERFACE ////////////////////////////////////////////////////////// - function offsetSet($key, $value) { - $this->save($key, $value); - } +/** + * @access private + */ +function elgg_get_filepath_cache() { + return elgg_get_system_cache(); +} +/** + * @access private + */ +function elgg_filepath_cache_reset() { + elgg_reset_system_cache(); +} +/** + * @access private + */ +function elgg_filepath_cache_save($type, $data) { + return elgg_save_system_cache($type, $data); +} +/** + * @access private + */ +function elgg_filepath_cache_load($type) { + return elgg_load_system_cache($type); +} +/** + * @access private + */ +function elgg_enable_filepath_cache() { + elgg_enable_system_cache(); +} +/** + * @access private + */ +function elgg_disable_filepath_cache() { + elgg_disable_system_cache(); +} - function offsetGet($key) { - return $this->load($key); - } +/* Simplecache */ - function offsetUnset($key) { - if ( isset($this->key) ) { - unset($this->key); - } +/** + * Registers a view to simple cache. + * + * Simple cache is a caching mechanism that saves the output of + * views and its extensions into a file. If the view is called + * by the {@link simplecache/view.php} file, the Elgg framework will + * not be loaded and the contents of the view will returned + * from file. + * + * @warning Simple cached views must take no parameters and return + * the same content no matter who is logged in. + * + * @example + * $blog_js = elgg_get_simplecache_url('js', 'blog/save_draft'); + * elgg_register_simplecache_view('js/blog/save_draft'); + * elgg_register_js('elgg.blog', $blog_js); + * elgg_load_js('elgg.blog'); + * + * @param string $viewname View name + * + * @return void + * @link http://docs.elgg.org/Views/Simplecache + * @see elgg_regenerate_simplecache() + * @since 1.8.0 + */ +function elgg_register_simplecache_view($viewname) { + global $CONFIG; + + if (!isset($CONFIG->views)) { + $CONFIG->views = new stdClass; } - function offsetExists($offset) { - return isset($this->$offset); + if (!isset($CONFIG->views->simplecache)) { + $CONFIG->views->simplecache = array(); } + + $CONFIG->views->simplecache[] = $viewname; } /** - * Shared memory cache description. - * Extends ElggCache with functions useful to shared memory style caches (static variables, memcache etc) + * Get the URL for the cached file + * + * @warning You must register the view with elgg_register_simplecache_view() + * for caching to work. See elgg_register_simplecache_view() for a full example. + * + * @param string $type The file type: css or js + * @param string $view The view name + * @return string + * @since 1.8.0 */ -abstract class ElggSharedMemoryCache extends ElggCache { - /** - * Namespace variable used to keep various bits of the cache - * separate. - * - * @var string - */ - private $namespace; - - /** - * Set the namespace of this cache. - * This is useful for cache types (like memcache or static variables) where there is one large - * flat area of memory shared across all instances of the cache. - * - * @param string $namespace - */ - public function setNamespace($namespace = "default") { - $this->namespace = $namespace; - } - - /** - * Get the namespace currently defined. - * - * @return string - */ - public function getNamespace() { - return $this->namespace; +function elgg_get_simplecache_url($type, $view) { + global $CONFIG; + $lastcache = (int)$CONFIG->lastcache; + $viewtype = elgg_get_viewtype(); + elgg_register_simplecache_view("$type/$view");// see #5302 + if (elgg_is_simplecache_enabled()) { + $url = elgg_get_site_url() . "cache/$type/$viewtype/$view.$lastcache.$type"; + } else { + $url = elgg_get_site_url() . "$type/$view.$lastcache.$type"; + $elements = array("view" => $viewtype); + $url = elgg_http_add_url_query_elements($url, $elements); } + + return $url; } /** - * ElggStaticVariableCache - * Dummy cache which stores values in a static array. Using this makes future replacements to other caching back - * ends (eg memcache) much easier. + * Regenerates the simple cache. + * + * @warning This does not invalidate the cache, but actively rebuilds it. + * + * @param string $viewtype Optional viewtype to regenerate. Defaults to all valid viewtypes. * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage API + * @return void + * @see elgg_register_simplecache_view() + * @since 1.8.0 */ -class ElggStaticVariableCache extends ElggSharedMemoryCache { - /** - * The cache. - * - * @var unknown_type - */ - private static $__cache; +function elgg_regenerate_simplecache($viewtype = NULL) { + global $CONFIG; - /** - * Create the variable cache. - * - * This function creates a variable cache in a static variable in memory, optionally with a given namespace (to avoid overlap). - * - * @param string $namespace The namespace for this cache to write to - note, namespaces of the same name are shared! - */ - function __construct($namespace = 'default') { - $this->setNamespace($namespace); - $this->clear(); + if (!isset($CONFIG->views->simplecache) || !is_array($CONFIG->views->simplecache)) { + return; } - public function save($key, $data) { - $namespace = $this->getNamespace(); + $lastcached = time(); - ElggStaticVariableCache::$__cache[$namespace][$key] = $data; + // @todo elgg_view() checks if the page set is done (isset($CONFIG->pagesetupdone)) and + // triggers an event if it's not. Calling elgg_view() here breaks submenus + // (at least) because the page setup hook is called before any + // contexts can be correctly set (since this is called before page_handler()). + // To avoid this, lie about $CONFIG->pagehandlerdone to force + // the trigger correctly when the first view is actually being output. + $CONFIG->pagesetupdone = TRUE; - return true; + if (!file_exists($CONFIG->dataroot . 'views_simplecache')) { + mkdir($CONFIG->dataroot . 'views_simplecache'); } - public function load($key, $offset = 0, $limit = null) { - $namespace = $this->getNamespace(); + if (isset($viewtype)) { + $viewtypes = array($viewtype); + } else { + $viewtypes = $CONFIG->view_types; + } + + $original_viewtype = elgg_get_viewtype(); + + // disable error reporting so we don't cache problems + $old_debug = elgg_get_config('debug'); + elgg_set_config('debug', null); - if (isset(ElggStaticVariableCache::$__cache[$namespace][$key])) { - return ElggStaticVariableCache::$__cache[$namespace][$key]; + foreach ($viewtypes as $viewtype) { + elgg_set_viewtype($viewtype); + foreach ($CONFIG->views->simplecache as $view) { + $viewcontents = elgg_view($view); + $viewname = md5(elgg_get_viewtype() . $view); + if ($handle = fopen($CONFIG->dataroot . 'views_simplecache/' . $viewname, 'w')) { + fwrite($handle, $viewcontents); + fclose($handle); + } } - return false; + datalist_set("simplecache_lastupdate_$viewtype", $lastcached); + datalist_set("simplecache_lastcached_$viewtype", $lastcached); } - public function delete($key) { - $namespace = $this->getNamespace(); + elgg_set_config('debug', $old_debug); + elgg_set_viewtype($original_viewtype); + + // needs to be set for links in html head + $CONFIG->lastcache = $lastcached; - unset(ElggStaticVariableCache::$__cache[$namespace][$key]); + unset($CONFIG->pagesetupdone); +} +/** + * Is simple cache enabled + * + * @return bool + * @since 1.8.0 + */ +function elgg_is_simplecache_enabled() { + if (elgg_get_config('simplecache_enabled')) { return true; } - public function clear() { - $namespace = $this->getNamespace(); + return false; +} - if (!isset(ElggStaticVariableCache::$__cache)) { - ElggStaticVariableCache::$__cache = array(); - } +/** + * Enables the simple cache. + * + * @access private + * @see elgg_register_simplecache_view() + * @return void + * @since 1.8.0 + */ +function elgg_enable_simplecache() { + global $CONFIG; - ElggStaticVariableCache::$__cache[$namespace] = array(); - } + datalist_set('simplecache_enabled', 1); + $CONFIG->simplecache_enabled = 1; + elgg_regenerate_simplecache(); } /** - * ElggFileCache - * Store cached data in a file store. + * Disables the simple cache. + * + * @warning Simplecache is also purged when disabled. * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage API + * @access private + * @see elgg_register_simplecache_view() + * @return void + * @since 1.8.0 */ -class ElggFileCache extends ElggCache { - /** - * Set the Elgg cache. - * - * @param string $cache_path The cache path. - * @param int $max_age Maximum age in seconds, 0 if no limit. - * @param int $max_size Maximum size of cache in seconds, 0 if no limit. - */ - function __construct($cache_path, $max_age = 0, $max_size = 0) { - $this->set_variable("cache_path", $cache_path); - $this->set_variable("max_age", $max_age); - $this->set_variable("max_size", $max_size); - - if ($cache_path=="") { - throw new ConfigurationException(elgg_echo('ConfigurationException:NoCachePath')); +function elgg_disable_simplecache() { + global $CONFIG; + if ($CONFIG->simplecache_enabled) { + datalist_set('simplecache_enabled', 0); + $CONFIG->simplecache_enabled = 0; + + // purge simple cache + if ($handle = opendir($CONFIG->dataroot . 'views_simplecache')) { + while (false !== ($file = readdir($handle))) { + if ($file != "." && $file != "..") { + unlink($CONFIG->dataroot . 'views_simplecache/' . $file); + } + } + closedir($handle); } } +} - /** - * Create and return a handle to a file. - * - * @param string $filename - * @param string $rw - */ - protected function create_file($filename, $rw = "rb") { - // Create a filename matrix - $matrix = ""; - $depth = strlen($filename); - if ($depth > 5) { - $depth = 5; - } - - // Create full path - $path = $this->get_variable("cache_path") . $matrix; - if (!is_dir($path)) { - mkdir($path, 0700, true); - } - - // Open the file - if ((!file_exists($path . $filename)) && ($rw=="rb")) { - return false; - } +/** + * Deletes all cached views in the simplecache and sets the lastcache and + * lastupdate time to 0 for every valid viewtype. + * + * @return bool + * @since 1.7.4 + */ +function elgg_invalidate_simplecache() { + global $CONFIG; - return fopen($path . $filename, $rw); + if (!isset($CONFIG->views->simplecache) || !is_array($CONFIG->views->simplecache)) { + return false; } - /** - * Create a sanitised filename for the file. - * - * @param string $filename - */ - protected function sanitise_filename($filename) { - // TODO : Writeme + $handle = opendir($CONFIG->dataroot . 'views_simplecache'); - return $filename; + if (!$handle) { + return false; } - /** - * Save a key - * - * @param string $key - * @param string $data - * @return boolean - */ - public function save($key, $data) { - $f = $this->create_file($this->sanitise_filename($key), "wb"); - if ($f) { - $result = fwrite($f, $data); - fclose($f); - - return $result; + // remove files. + $return = true; + while (false !== ($file = readdir($handle))) { + if ($file != "." && $file != "..") { + $return &= unlink($CONFIG->dataroot . 'views_simplecache/' . $file); } - - return false; } + closedir($handle); - /** - * Load a key - * - * @param string $key - * @param int $offset - * @param int $limit - * @return string - */ - public function load($key, $offset = 0, $limit = null) { - $f = $this->create_file($this->sanitise_filename($key)); - if ($f) { - //fseek($f, $offset); - if (!$limit) { - $limit = -1; - } - $data = stream_get_contents($f, $limit, $offset); - - fclose($f); - - return $data; - } + // reset cache times + $viewtypes = $CONFIG->view_types; + if (!is_array($viewtypes)) { return false; } - /** - * Invalidate a given key. - * - * @param string $key - * @return bool - */ - public function delete($key) { - $dir = $this->get_variable("cache_path"); - - if (file_exists($dir.$key)) { - return unlink($dir.$key); - } - return TRUE; - } - - public function clear() { - // TODO : writeme + foreach ($viewtypes as $viewtype) { + $return &= datalist_set("simplecache_lastupdate_$viewtype", 0); + $return &= datalist_set("simplecache_lastcached_$viewtype", 0); } - public function __destruct() { - // TODO: Check size and age, clean up accordingly - $size = 0; - $dir = $this->get_variable("cache_path"); + return $return; +} - // Short circuit if both size and age are unlimited - if (($this->get_variable("max_age")==0) && ($this->get_variable("max_size")==0)) { - return; - } +/** + * @see elgg_reset_system_cache() + * @access private + */ +function _elgg_load_cache() { + global $CONFIG; - $exclude = array(".",".."); + $CONFIG->system_cache_loaded = false; - $files = scandir($dir); - if (!$files) { - throw new IOException(sprintf(elgg_echo('IOException:NotDirectory'), $dir)); - } + $CONFIG->views = new stdClass(); + $data = elgg_load_system_cache('view_locations'); + if (!is_string($data)) { + return; + } + $CONFIG->views->locations = unserialize($data); + + $data = elgg_load_system_cache('view_types'); + if (!is_string($data)) { + return; + } + $CONFIG->view_types = unserialize($data); - // Perform cleanup - foreach ($files as $f) { - if (!in_array($f, $exclude)) { - $stat = stat($dir.$f); + $CONFIG->system_cache_loaded = true; +} - // Add size - $size .= $stat['size']; +/** + * @access private + */ +function _elgg_cache_init() { + global $CONFIG; + + $viewtype = elgg_get_viewtype(); + + // Regenerate the simple cache if expired. + // Don't do it on upgrade because upgrade does it itself. + // @todo - move into function and perhaps run off init system event + if (!defined('UPGRADING')) { + $lastupdate = datalist_get("simplecache_lastupdate_$viewtype"); + $lastcached = datalist_get("simplecache_lastcached_$viewtype"); + if ($lastupdate == 0 || $lastcached < $lastupdate) { + elgg_regenerate_simplecache($viewtype); + $lastcached = datalist_get("simplecache_lastcached_$viewtype"); + } + $CONFIG->lastcache = $lastcached; + } - // Is this older than my maximum date? - if (($this->get_variable("max_age")>0) && (time() - $stat['mtime'] > $this->get_variable("max_age"))) { - unlink($dir.$f); - } + // cache system data if enabled and not loaded + if ($CONFIG->system_cache_enabled && !$CONFIG->system_cache_loaded) { + elgg_save_system_cache('view_locations', serialize($CONFIG->views->locations)); + elgg_save_system_cache('view_types', serialize($CONFIG->view_types)); + } - // TODO: Size - } + if ($CONFIG->system_cache_enabled && !$CONFIG->i18n_loaded_from_cache) { + reload_all_translations(); + foreach ($CONFIG->translations as $lang => $map) { + elgg_save_system_cache("$lang.lang", serialize($map)); } } } + +elgg_register_event_handler('ready', 'system', '_elgg_cache_init'); diff --git a/engine/lib/calendar.php b/engine/lib/calendar.php index a9d6dfadf..e6f95934c 100644 --- a/engine/lib/calendar.php +++ b/engine/lib/calendar.php @@ -2,45 +2,21 @@ /** * Elgg calendar / entity / event functions. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - -/** - * Calendar interface for events. + * @package Elgg.Core + * @subpackage Calendar * + * @todo Implement or remove */ -interface Notable { - /** - * 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); - - /** - * Return the start timestamp. - */ - public function getCalendarStartTime(); - - /** - * Return the end timestamp. - */ - public function getCalendarEndTime(); -} /** * Return a timestamp for the start of a given day (defaults today). * + * @param int $day Day + * @param int $month Month + * @param int $year Year + * + * @return int + * @access private */ function get_day_start($day = null, $month = null, $year = null) { return mktime(0, 0, 0, $month, $day, $year); @@ -49,6 +25,12 @@ function get_day_start($day = null, $month = null, $year = null) { /** * Return a timestamp for the end of a given day (defaults today). * + * @param int $day Day + * @param int $month Month + * @param int $year Year + * + * @return int + * @access private */ function get_day_end($day = null, $month = null, $year = null) { return mktime(23, 59, 59, $month, $day, $year); @@ -57,19 +39,26 @@ function get_day_end($day = null, $month = null, $year = null) { /** * Return the notable entities for a given time period. * - * @param int $start_time The start time as a unix timestamp. - * @param int $end_time The end time as a unix timestamp. - * @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). + * @todo this function also accepts an array(type => subtypes) for 3rd arg. Should we document this? + * + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @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 instead of entities. Defaults to false. + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any. + * @param mixed $container_guid Container or containers to get entities from (default: any). + * + * @return array|false + * @access private */ -function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", $owner_guid = 0, $order_by = "asc", $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = null) { +function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "asc", $limit = 10, $offset = 0, $count = false, $site_guid = 0, +$container_guid = null) { global $CONFIG; if ($subtype === false || $subtype === null || $subtype === 0) { @@ -91,15 +80,17 @@ function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", if (is_array($type)) { $tempwhere = ""; if (sizeof($type)) { - foreach($type as $typekey => $subtypearray) { - foreach($subtypearray as $subtypeval) { + foreach ($type as $typekey => $subtypearray) { + foreach ($subtypearray as $subtypeval) { $typekey = sanitise_string($typekey); if (!empty($subtypeval)) { $subtypeval = (int) get_subtype_id($typekey, $subtypeval); } else { $subtypeval = 0; } - if (!empty($tempwhere)) $tempwhere .= " or "; + if (!empty($tempwhere)) { + $tempwhere .= " or "; + } $tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; } } @@ -115,7 +106,7 @@ function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", $where[] = "e.type='$type'"; } - if ($subtype!=="") { + if ($subtype !== "") { $where[] = "e.subtype=$subtype"; } } @@ -128,8 +119,8 @@ function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", } 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 = implode(",",$owner_guid); // - $where[] = "e.owner_guid in ({$owner_guid})" ; // + $owner_guid = implode(",", $owner_guid); + $where[] = "e.owner_guid in ({$owner_guid})"; } if (is_null($container_guid)) { $container_guid = $owner_array; @@ -142,8 +133,10 @@ function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", 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) . ")"; + 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}"; @@ -195,21 +188,26 @@ function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", /** * Return the notable entities for a given time period based on an item of metadata. * - * @param int $start_time The start time as a unix timestamp. - * @param int $end_time The end time as a unix timestamp. - * @param mixed $meta_name - * @param mixed $meta_value - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' * @param string $entity_subtype The subtype of the entity. - * @param int $limit - * @param int $offset - * @param string $order_by Optional ordering. - * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. - * @param true|false $count If set to true, returns the total number of entities rather than a list. (Default: false) + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any. + * @param bool $count If true, returns count instead of entities. (Default: false) * * @return int|array A list of entities, or a count if $count is set to true + * @access private */ -function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $meta_value = "", $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { +function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $meta_value = "", +$entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", +$site_guid = 0, $count = false) { + global $CONFIG; $meta_n = get_metastring_id($meta_name); @@ -227,7 +225,7 @@ function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $order_by = sanitise_string($order_by); $site_guid = (int) $site_guid; if ((is_array($owner_guid) && (count($owner_guid)))) { - foreach($owner_guid as $key => $guid) { + foreach ($owner_guid as $key => $guid) { $owner_guid[$key] = (int) $guid; } } else { @@ -242,7 +240,7 @@ function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $where = array(); - if ($entity_type!="") { + if ($entity_type != "") { $where[] = "e.type='$entity_type'"; } @@ -250,11 +248,11 @@ function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $where[] = "e.subtype=$entity_subtype"; } - if ($meta_name!="") { + if ($meta_name != "") { $where[] = "m.name_id='$meta_n'"; } - if ($meta_value!="") { + if ($meta_value != "") { $where[] = "m.value_id='$meta_v'"; } @@ -263,7 +261,7 @@ function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, } if (is_array($owner_guid)) { - $where[] = "e.container_guid in (".implode(",",$owner_guid).")"; + $where[] = "e.container_guid in (" . implode(",", $owner_guid) . ")"; } else if ($owner_guid > 0) { $where[] = "e.container_guid = {$owner_guid}"; } @@ -290,7 +288,9 @@ function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $query = "SELECT count(distinct e.guid) as total "; } - $query .= "from {$CONFIG->dbprefix}entities e JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid $cal_join where"; + $query .= "from {$CONFIG->dbprefix}entities e" + . " JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid $cal_join where"; + foreach ($where as $w) { $query .= " $w and "; } @@ -315,22 +315,29 @@ function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, /** * Return the notable entities for a given time period based on their relationship. * - * @param int $start_time The start time as a unix timestamp. - * @param int $end_time The end time as a unix timestamp. - * @param string $relationship The relationship eg "friends_of" - * @param int $relationship_guid The guid of the entity to use query - * @param bool $inverse_relationship Reverse the normal function of the query to instead say "give me all entities for whome $relationship_guid is a $relationship of" - * @param string $type - * @param string $subtype - * @param int $owner_guid - * @param string $order_by - * @param int $limit - * @param int $offset - * @param boolean $count Set to true if you want to count the number of entities instead (default 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 $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Reverse the normal function of the query to say + * "give me all entities for whom $relationship_guid is a + * $relationship of" + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param string $order_by Optional Order by + * @param int $limit Limit + * @param int $offset Offset + * @param boolean $count If true returns a count of entities (default false) + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any + * * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private */ -function get_noteable_entities_from_relationship($start_time, $end_time, $relationship, $relationship_guid, $inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { +function get_noteable_entities_from_relationship($start_time, $end_time, $relationship, +$relationship_guid, $inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + global $CONFIG; $start_time = (int)$start_time; @@ -356,11 +363,12 @@ function get_noteable_entities_from_relationship($start_time, $end_time, $relati $where = array(); - if ($relationship!="") { + if ($relationship != "") { $where[] = "r.relationship='$relationship'"; } if ($relationship_guid) { - $where[] = ($inverse_relationship ? "r.guid_two='$relationship_guid'" : "r.guid_one='$relationship_guid'"); + $where[] = $inverse_relationship ? + "r.guid_two='$relationship_guid'" : "r.guid_one='$relationship_guid'"; } if ($type != "") { $where[] = "e.type='$type'"; @@ -401,7 +409,9 @@ function get_noteable_entities_from_relationship($start_time, $end_time, $relati } else { $query = "SELECT distinct e.* "; } - $query .= " from {$CONFIG->dbprefix}entity_relationships r JOIN {$CONFIG->dbprefix}entities e on $joinon $cal_join where "; + $query .= " from {$CONFIG->dbprefix}entity_relationships r" + . " JOIN {$CONFIG->dbprefix}entities e on $joinon $cal_join where "; + foreach ($where as $w) { $query .= " $w and "; } @@ -421,66 +431,87 @@ function get_noteable_entities_from_relationship($start_time, $end_time, $relati /** * Get all entities for today. * - * @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). + * @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 If true returns a count of entities (default false) + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any + * @param mixed $container_guid Container(s) to get entities from (default: any). + * + * @return array|false + * @access private */ -function get_todays_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = null) { +function get_todays_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", +$limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = null) { + $day_start = get_day_start(); $day_end = get_day_end(); - return get_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $order_by, $limit, $offset, $count, $site_guid, $container_guid); + return get_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $order_by, + $limit, $offset, $count, $site_guid, $container_guid); } /** * Get entities for today from metadata. * - * @param mixed $meta_name - * @param mixed $meta_value - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' * @param string $entity_subtype The subtype of the entity. - * @param int $limit - * @param int $offset - * @param string $order_by Optional ordering. - * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. - * @param true|false $count If set to true, returns the total number of entities rather than a list. (Default: false) + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any. + * @param bool $count If true, returns count instead of entities. (Default: false) * * @return int|array A list of entities, or a count if $count is set to true + * @access private */ -function get_todays_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { +function get_todays_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", +$entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, +$count = false) { + $day_start = get_day_start(); $day_end = get_day_end(); - return get_notable_entities_from_metadata($day_start, $day_end, $meta_name, $meta_value, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, $order_by, $site_guid, $count); + return get_notable_entities_from_metadata($day_start, $day_end, $meta_name, $meta_value, + $entity_type, $entity_subtype, $owner_guid, $limit, $offset, $order_by, $site_guid, $count); } /** * Get entities for today from a relationship * - * @param string $relationship The relationship eg "friends_of" - * @param int $relationship_guid The guid of the entity to use query - * @param bool $inverse_relationship Reverse the normal function of the query to instead say "give me all entities for whome $relationship_guid is a $relationship of" - * @param string $type - * @param string $subtype - * @param int $owner_guid - * @param string $order_by - * @param int $limit - * @param int $offset - * @param boolean $count Set to true if you want to count the number of entities instead (default false) - * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Reverse the normal function of the query to say + * "give me all entities for whom $relationship_guid is a + * $relationship of" + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param string $order_by Optional Order by + * @param int $limit Limit + * @param int $offset Offset + * @param boolean $count If true returns a count of entities (default false) + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any + * * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private */ -function get_todays_entities_from_relationship($relationship, $relationship_guid, $inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { +function get_todays_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + $day_start = get_day_start(); $day_end = get_day_end(); - return get_notable_entities_from_relationship($day_start, $day_end, $relationship, $relationship_guid, $inverse_relationship, $type, $subtype, $owner_guid, $order_by, $limit, $offset, $count, $site_guid); + return get_notable_entities_from_relationship($day_start, $day_end, $relationship, + $relationship_guid, $inverse_relationship, $type, $subtype, $owner_guid, $order_by, + $limit, $offset, $count, $site_guid); } /** @@ -488,23 +519,31 @@ function get_todays_entities_from_relationship($relationship, $relationship_guid * * @see elgg_view_entity_list * - * @param int $start_time The start time as a unix timestamp. - * @param int $end_time The end time as a unix timestamp. - * @param string $type The type of entity (eg "user", "object" etc) - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * @param int $limit The number of entities to display per page (default: 10) - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow gallery view - * @param true|false $pagination Display pagination? Default: true + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to return; 10 by default + * @param boolean $fullview Whether or not to display the full view (default: true) + * @param boolean $listtypetoggle Whether or not to allow gallery view + * @param boolean $navigation Display pagination? Default: true + * * @return string A viewable list of entities + * @access private */ -function list_notable_entities($start_time, $end_time, $type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false, $navigation = true) { +function list_notable_entities($start_time, $end_time, $type= "", $subtype = "", $owner_guid = 0, +$limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { + $offset = (int) get_input('offset'); - $count = get_notable_entities($start_time, $end_time, $type, $subtype, $owner_guid, "", $limit, $offset, true); - $entities = get_notable_entities($start_time, $end_time,$type, $subtype, $owner_guid, "", $limit, $offset); + $count = get_notable_entities($start_time, $end_time, $type, $subtype, + $owner_guid, "", $limit, $offset, true); + + $entities = get_notable_entities($start_time, $end_time, $type, $subtype, + $owner_guid, "", $limit, $offset); - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $navigation); + return elgg_view_entity_list($entities, $count, $offset, $limit, + $fullview, $listtypetoggle, $navigation); } /** @@ -512,18 +551,23 @@ function list_notable_entities($start_time, $end_time, $type= "", $subtype = "", * * @see list_notable_entities * - * @param string $type The type of entity (eg "user", "object" etc) - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * @param int $limit The number of entities to display per page (default: 10) - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow gallery view - * @param true|false $pagination Display pagination? Default: true + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to return; 10 by default + * @param boolean $fullview Whether or not to display the full view (default: true) + * @param boolean $listtypetoggle Whether or not to allow gallery view + * @param boolean $navigation Display pagination? Default: true + * * @return string A viewable list of entities + * @access private */ -function list_todays_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false, $navigation = true) { +function list_todays_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, +$fullview = true, $listtypetoggle = false, $navigation = true) { + $day_start = get_day_start(); $day_end = get_day_end(); - return list_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $limit, $fullview, $viewtypetoggle, $navigation); -}
\ No newline at end of file + return list_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $limit, + $fullview, $listtypetoggle, $navigation); +} diff --git a/engine/lib/configuration.php b/engine/lib/configuration.php index 17b3cd04a..55e5bbd36 100644 --- a/engine/lib/configuration.php +++ b/engine/lib/configuration.php @@ -1,49 +1,434 @@ <?php /** - * Elgg configuration library - * Contains functions for managing system configuration + * Elgg configuration procedural code. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * Includes functions for manipulating the configuration values stored in the database + * Plugin authors should use the {@link elgg_get_config()}, {@link elgg_set_config()}, + * {@link elgg_save_config()}, and {@unset_config()} functions to access or update + * config values. + * + * Elgg's configuration is split among 2 tables and 1 file: + * - dbprefix_config + * - dbprefix_datalists + * - engine/settings.php (See {@link settings.example.php}) + * + * Upon system boot, all values in dbprefix_config are read into $CONFIG. + * + * @package Elgg.Core + * @subpackage Configuration + */ + +/** + * Get the URL for the current (or specified) site + * + * @param int $site_guid The GUID of the site whose URL we want to grab + * @return string + * @since 1.8.0 + */ +function elgg_get_site_url($site_guid = 0) { + if ($site_guid == 0) { + global $CONFIG; + return $CONFIG->wwwroot; + } + + $site = get_entity($site_guid); + + if (!$site instanceof ElggSite) { + return false; + } + /* @var ElggSite $site */ + + return $site->url; +} + +/** + * Get the plugin path for this installation + * + * @return string + * @since 1.8.0 + */ +function elgg_get_plugins_path() { + global $CONFIG; + return $CONFIG->pluginspath; +} + +/** + * Get the data directory path for this installation + * + * @return string + * @since 1.8.0 + */ +function elgg_get_data_path() { + global $CONFIG; + return $CONFIG->dataroot; +} + +/** + * Get the root directory path for this installation + * + * @return string + * @since 1.8.0 + */ +function elgg_get_root_path() { + global $CONFIG; + return $CONFIG->path; +} + +/** + * Get an Elgg configuration value + * + * @param string $name Name of the configuration value + * @param int $site_guid NULL for installation setting, 0 for default site + * + * @return mixed Configuration value or null if it does not exist + * @since 1.8.0 + */ +function elgg_get_config($name, $site_guid = 0) { + global $CONFIG; + + $name = trim($name); + + if (isset($CONFIG->$name)) { + return $CONFIG->$name; + } + + if ($site_guid === null) { + // installation wide setting + $value = datalist_get($name); + } else { + // hit DB only if we're not sure if value exists or not + if (!isset($CONFIG->site_config_loaded)) { + // site specific setting + if ($site_guid == 0) { + $site_guid = (int) $CONFIG->site_id; + } + $value = get_config($name, $site_guid); + } else { + $value = null; + } + } + + // @todo document why we don't cache false + if ($value === false) { + return null; + } + + $CONFIG->$name = $value; + return $value; +} + +/** + * Set an Elgg configuration value + * + * @warning This does not persist the configuration setting. Use elgg_save_config() + * + * @param string $name Name of the configuration value + * @param mixed $value Value + * + * @return void + * @since 1.8.0 + */ +function elgg_set_config($name, $value) { + global $CONFIG; + + $name = trim($name); + + $CONFIG->$name = $value; +} + +/** + * Save a configuration setting + * + * @param string $name Configuration name (cannot be greater than 255 characters) + * @param mixed $value Configuration value. Should be string for installation setting + * @param int $site_guid NULL for installation setting, 0 for default site + * + * @return bool + * @since 1.8.0 + */ +function elgg_save_config($name, $value, $site_guid = 0) { + global $CONFIG; + + $name = trim($name); + + if (strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); + return false; + } + + elgg_set_config($name, $value); + + if ($site_guid === NULL) { + if (is_array($value) || is_object($value)) { + return false; + } + return datalist_set($name, $value); + } else { + if ($site_guid == 0) { + $site_guid = (int) $CONFIG->site_id; + } + return set_config($name, $value, $site_guid); + } +} + +/** + * Check that installation has completed and the database is populated. + * + * @throws InstallationException|DatabaseException + * @return void + * @access private + */ +function verify_installation() { + global $CONFIG; + + if (isset($CONFIG->installed)) { + return; + } + + try { + $dblink = get_db_link('read'); + if (!$dblink) { + throw new DatabaseException(); + } + + mysql_query("SELECT value FROM {$CONFIG->dbprefix}datalists WHERE name = 'installed'", $dblink); + if (mysql_errno($dblink) > 0) { + throw new DatabaseException(); + } + + $CONFIG->installed = true; + + } catch (DatabaseException $e) { + throw new InstallationException(elgg_echo('InstallationException:SiteNotInstalled')); + } +} + +/** + * An array of key value pairs from the datalists table. + * + * Used as a cache in datalist functions. + * + * @global array $DATALIST_CACHE + */ +$DATALIST_CACHE = array(); + +/** + * Get the value of a datalist element. + * + * @internal Datalists are stored in the datalist table. + * + * @tip Use datalists to store information common to a full installation. + * + * @param string $name The name of the datalist + * @return string|null|false String if value exists, null if doesn't, false on error + * @access private + */ +function datalist_get($name) { + global $CONFIG, $DATALIST_CACHE; + + $name = trim($name); + + // cannot store anything longer than 255 characters in db, so catch here + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); + return false; + } + + $name = sanitise_string($name); + if (isset($DATALIST_CACHE[$name])) { + return $DATALIST_CACHE[$name]; + } + + // If memcache enabled then cache value in memcache + $value = null; + static $datalist_memcache; + if ((!$datalist_memcache) && (is_memcache_available())) { + $datalist_memcache = new ElggMemcache('datalist_memcache'); + } + if ($datalist_memcache) { + $value = $datalist_memcache->load($name); + } + if ($value) { + return $value; + } + + // [Marcus Povey 20090217 : Now retrieving all datalist values on first + // load as this saves about 9 queries per page] + // This also causes OOM problems when the datalists table is large + // @todo make a list of datalists that we want to get in one grab + $result = get_data("SELECT * from {$CONFIG->dbprefix}datalists"); + if ($result) { + foreach ($result as $row) { + $DATALIST_CACHE[$row->name] = $row->value; + + // Cache it if memcache is available + if ($datalist_memcache) { + $datalist_memcache->save($row->name, $row->value); + } + } + + if (isset($DATALIST_CACHE[$name])) { + return $DATALIST_CACHE[$name]; + } + } + + return null; +} + +/** + * Set the value for a datalist element. + * + * @param string $name The name of the datalist + * @param string $value The new value + * + * @return bool + * @access private */ +function datalist_set($name, $value) { + global $CONFIG, $DATALIST_CACHE; + + // cannot store anything longer than 255 characters in db, so catch before we set + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); + return false; + } + + $sanitised_name = sanitise_string($name); + $sanitised_value = sanitise_string($value); + + // If memcache is available then invalidate the cached copy + static $datalist_memcache; + if ((!$datalist_memcache) && (is_memcache_available())) { + $datalist_memcache = new ElggMemcache('datalist_memcache'); + } + + if ($datalist_memcache) { + $datalist_memcache->delete($name); + } + + $success = insert_data("INSERT into {$CONFIG->dbprefix}datalists" + . " set name = '{$sanitised_name}', value = '{$sanitised_value}'" + . " ON DUPLICATE KEY UPDATE value='{$sanitised_value}'"); + + if ($success !== FALSE) { + $DATALIST_CACHE[$name] = $value; + return true; + } else { + return false; + } +} + +/** + * Run a function one time per installation. + * + * If you pass a timestamp as the second argument, it will run the function + * only if (i) it has never been run before or (ii) the timestamp is >= + * the last time it was run. + * + * @warning Functions are determined by their name. If you change the name of a function + * it will be run again. + * + * @tip Use $timelastupdatedcheck in your plugins init function to perform automated + * upgrades. Schedule a function to run once and pass the timestamp of the new release. + * This will cause the run once function to be run on all installations. To perform + * additional upgrades, create new functions for each release. + * + * @warning The function name cannot be longer than 255 characters long due to + * the current schema for the datalist table. + * + * @internal A datalist entry $functioname is created with the value of time(). + * + * @param string $functionname The name of the function you want to run. + * @param int $timelastupdatedcheck A UNIX timestamp. If time() is > than this, + * this function will be run again. + * + * @return bool + */ +function run_function_once($functionname, $timelastupdatedcheck = 0) { + $lastupdated = datalist_get($functionname); + if ($lastupdated) { + $lastupdated = (int) $lastupdated; + } elseif ($lastupdated !== false) { + $lastupdated = 0; + } else { + // unable to check datalist + return false; + } + if (is_callable($functionname) && $lastupdated <= $timelastupdatedcheck) { + $functionname(); + datalist_set($functionname, time()); + return true; + } else { + return false; + } +} /** - * Unset a config option. + * Removes a config setting. + * + * @internal + * These settings are stored in the dbprefix_config table and read during system + * boot into $CONFIG. + * + * @param string $name The name of the field. + * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default). * - * @param string $name The name of the field. - * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default). - * @return mixed + * @return int|false The number of affected rows or false on error. + * + * @see get_config() + * @see set_config() */ function unset_config($name, $site_guid = 0) { global $CONFIG; - $name = mysql_real_escape_string($name); + if (isset($CONFIG->$name)) { + unset($CONFIG->$name); + } + + $name = sanitise_string($name); $site_guid = (int) $site_guid; if ($site_guid == 0) { $site_guid = (int) $CONFIG->site_id; } - return delete_data("delete from {$CONFIG->dbprefix}config where name='$name' and site_guid=$site_guid"); + $query = "delete from {$CONFIG->dbprefix}config where name='$name' and site_guid=$site_guid"; + return delete_data($query); } /** - * Sets a configuration value + * Add or update a config setting. * - * @param string $name The name of the configuration value - * @param string $value Its value - * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default) - * @return false|int 1 or false depending on success or failure + * If the config name already exists, it will be updated to the new value. + * + * @internal + * These settings are stored in the dbprefix_config table and read during system + * boot into $CONFIG. + * + * @param string $name The name of the configuration value + * @param string $value Its value + * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default) + * + * @return bool + * @todo The config table doens't have numeric primary keys so insert_data returns 0. + * @todo Use "INSERT ... ON DUPLICATE KEY UPDATE" instead of trying to delete then add. + * @see unset_config() + * @see get_config() + * @access private */ function set_config($name, $value, $site_guid = 0) { global $CONFIG; + $name = trim($name); + + // cannot store anything longer than 255 characters in db, so catch before we set + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); + return false; + } + // Unset existing - unset_config($name,$site_guid); + unset_config($name, $site_guid); - $name = mysql_real_escape_string($name); - $value = mysql_real_escape_string($value); $site_guid = (int) $site_guid; if ($site_guid == 0) { $site_guid = (int) $CONFIG->site_id; @@ -51,42 +436,90 @@ function set_config($name, $value, $site_guid = 0) { $CONFIG->$name = $value; $value = sanitise_string(serialize($value)); - return insert_data("insert into {$CONFIG->dbprefix}config set name = '{$name}', value = '{$value}', site_guid = {$site_guid}"); + $query = "insert into {$CONFIG->dbprefix}config" + . " set name = '{$name}', value = '{$value}', site_guid = {$site_guid}"; + $result = insert_data($query); + return $result !== false; } /** * Gets a configuration value * - * @param string $name The name of the config value - * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default) - * @return mixed|false Depending on success + * @internal + * These settings are stored in the dbprefix_config table and read during system + * boot into $CONFIG. + * + * @param string $name The name of the config value + * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default) + * + * @return mixed|null + * @see set_config() + * @see unset_config() + * @access private */ function get_config($name, $site_guid = 0) { global $CONFIG; + $name = sanitise_string($name); + $site_guid = (int) $site_guid; + + // check for deprecated values. + // @todo might be a better spot to define this? + $new_name = false; + switch($name) { + case 'viewpath': + $new_name = 'view_path'; + $dep_version = 1.8; + break; + + case 'pluginspath': + $new_name = 'plugins_path'; + $dep_version = 1.8; + break; + + case 'sitename': + $new_name = 'site_name'; + $dep_version = 1.8; + break; + } + + // @todo these haven't really been implemented in Elgg 1.8. Complete in 1.9. + // show dep message + if ($new_name) { + // $msg = "Config value $name has been renamed as $new_name"; + $name = $new_name; + // elgg_deprecated_notice($msg, $dep_version); + } + + // decide from where to return the value if (isset($CONFIG->$name)) { return $CONFIG->$name; } - $name = mysql_real_escape_string($name); - $site_guid = (int) $site_guid; + if ($site_guid == 0) { $site_guid = (int) $CONFIG->site_id; } - if ($result = get_data_row("SELECT value FROM {$CONFIG->dbprefix}config - WHERE name = '{$name}' and site_guid = {$site_guid}")) { + + $result = get_data_row("SELECT value FROM {$CONFIG->dbprefix}config + WHERE name = '{$name}' and site_guid = {$site_guid}"); + + if ($result) { $result = $result->value; $result = unserialize($result->value); $CONFIG->$name = $result; return $result; } - return false; + return null; } /** - * Gets all the configuration details in the config database for a given site. + * Loads all configuration values from the dbprefix_config table into $CONFIG. * * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default) + * + * @return bool + * @access private */ function get_all_config($site_guid = 0) { global $CONFIG; @@ -94,10 +527,10 @@ function get_all_config($site_guid = 0) { $site_guid = (int) $site_guid; if ($site_guid == 0) { - $site_guid = (int) $CONFIG->site_id; + $site_guid = (int) $CONFIG->site_guid; } - if ($result = get_data("SELECT * from {$CONFIG->dbprefix}config where site_guid = {$site_guid}")) { + if ($result = get_data("SELECT * FROM {$CONFIG->dbprefix}config WHERE site_guid = $site_guid")) { foreach ($result as $r) { $name = $r->name; $value = $r->value; @@ -110,104 +543,90 @@ function get_all_config($site_guid = 0) { } /** - * If certain configuration elements don't exist, autodetect sensible defaults - * - * @uses $CONFIG The main configuration global + * Loads configuration related to this site * + * This loads from the config database table and the site entity + * @access private */ -function set_default_config() { +function _elgg_load_site_config() { global $CONFIG; - if (empty($CONFIG->path)) { - $CONFIG->path = str_replace("\\","/",dirname(dirname(dirname(__FILE__)))) . "/"; - } - - if (empty($CONFIG->viewpath)) { - $CONFIG->viewpath = $CONFIG->path . "views/"; + $CONFIG->site_guid = (int) datalist_get('default_site'); + $CONFIG->site_id = $CONFIG->site_guid; + $CONFIG->site = get_entity($CONFIG->site_guid); + if (!$CONFIG->site) { + throw new InstallationException(elgg_echo('InstallationException:SiteNotInstalled')); } - if (empty($CONFIG->pluginspath)) { - $CONFIG->pluginspath = $CONFIG->path . "mod/"; - } - - if (empty($CONFIG->wwwroot)) { - /* - $CONFIG->wwwroot = "http://" . $_SERVER['SERVER_NAME']; + $CONFIG->wwwroot = $CONFIG->site->url; + $CONFIG->sitename = $CONFIG->site->name; + $CONFIG->sitedescription = $CONFIG->site->description; + $CONFIG->siteemail = $CONFIG->site->email; + $CONFIG->url = $CONFIG->wwwroot; - $request = $_SERVER['REQUEST_URI']; + get_all_config(); + // gives hint to elgg_get_config function how to approach missing values + $CONFIG->site_config_loaded = true; +} - if (strripos($request,"/") < (strlen($request) - 1)) { - // addressing a file directly, not a dir - $request = substr($request, 0, strripos($request,"/")+1); - } +/** + * Loads configuration related to Elgg as an application + * + * This loads from the datalists database table + * @access private + */ +function _elgg_load_application_config() { + global $CONFIG; - $CONFIG->wwwroot .= $request; - */ - $pathpart = str_replace("//","/",str_replace($_SERVER['DOCUMENT_ROOT'],"",$CONFIG->path)); - if (substr($pathpart,0,1) != "/") { - $pathpart = "/" . $pathpart; + $install_root = str_replace("\\", "/", dirname(dirname(dirname(__FILE__)))); + $defaults = array( + 'path' => "$install_root/", + 'view_path' => "$install_root/views/", + 'plugins_path' => "$install_root/mod/", + 'language' => 'en', + + // compatibility with old names for plugins not using elgg_get_config() + 'viewpath' => "$install_root/views/", + 'pluginspath' => "$install_root/mod/", + ); + + foreach ($defaults as $name => $value) { + if (empty($CONFIG->$name)) { + $CONFIG->$name = $value; } - $CONFIG->wwwroot = "http://" . $_SERVER['HTTP_HOST'] . $pathpart; } - if (empty($CONFIG->url)) { - $CONFIG->url = $CONFIG->wwwroot; + $path = datalist_get('path'); + if (!empty($path)) { + $CONFIG->path = $path; } - - if (empty($CONFIG->sitename)) { - $CONFIG->sitename = "New Elgg site"; + $dataroot = datalist_get('dataroot'); + if (!empty($dataroot)) { + $CONFIG->dataroot = $dataroot; } - - if (empty($CONFIG->language)) { - $CONFIG->language = "en"; + $simplecache_enabled = datalist_get('simplecache_enabled'); + if ($simplecache_enabled !== false) { + $CONFIG->simplecache_enabled = $simplecache_enabled; + } else { + $CONFIG->simplecache_enabled = 1; + } + $system_cache_enabled = datalist_get('system_cache_enabled'); + if ($system_cache_enabled !== false) { + $CONFIG->system_cache_enabled = $system_cache_enabled; + } else { + $CONFIG->system_cache_enabled = 1; } -} -/** - * Function that provides some config initialisation on system init - * - */ -function configuration_init() { - global $CONFIG; + // initialize context here so it is set before the get_input call + $CONFIG->context = array(); - if (is_installed() || is_db_installed()) { - $path = datalist_get('path'); - if (!empty($path)) { - $CONFIG->path = $path; - } - $dataroot = datalist_get('dataroot'); - if (!empty($dataroot)) { - $CONFIG->dataroot = $dataroot; - } - $simplecache_enabled = datalist_get('simplecache_enabled'); - if ($simplecache_enabled !== false) { - $CONFIG->simplecache_enabled = $simplecache_enabled; - } else { - $CONFIG->simplecache_enabled = 1; - } - $viewpath_cache_enabled = datalist_get('viewpath_cache_enabled'); - if ($viewpath_cache_enabled !== false) { - $CONFIG->viewpath_cache_enabled = $viewpath_cache_enabled; - } else { - $CONFIG->viewpath_cache_enabled = 1; - } - if (isset($CONFIG->site) && ($CONFIG->site instanceof ElggSite)) { - $CONFIG->wwwroot = $CONFIG->site->url; - $CONFIG->sitename = $CONFIG->site->name; - $CONFIG->sitedescription = $CONFIG->site->description; - $CONFIG->siteemail = $CONFIG->site->email; - } - $CONFIG->url = $CONFIG->wwwroot; + // needs to be set before system, init for links in html head + $viewtype = get_input('view', 'default'); + $lastcached = datalist_get("simplecache_lastcached_$viewtype"); + $CONFIG->lastcache = $lastcached; - // Load default settings from database - get_all_config(); + $CONFIG->i18n_loaded_from_cache = false; - return true; - } + // this must be synced with the enum for the entities table + $CONFIG->entity_types = array('group', 'object', 'site', 'user'); } - -/** - * Register config_init - */ - -register_elgg_event_handler('boot', 'system', 'configuration_init', 10);
\ No newline at end of file diff --git a/engine/lib/cron.php b/engine/lib/cron.php index b4952e2ee..4f3d05b93 100644 --- a/engine/lib/cron.php +++ b/engine/lib/cron.php @@ -4,54 +4,86 @@ * * @package Elgg * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ */ -/** The cron exception. */ -class CronException extends Exception {} - /** - * Initialisation + * Cron initialization * + * @return void + * @access private */ function cron_init() { // Register a pagehandler for cron - register_page_handler('cron','cron_page_handler'); + elgg_register_page_handler('cron', 'cron_page_handler'); + + // register a hook for Walled Garden public pages + elgg_register_plugin_hook_handler('public_pages', 'walled_garden', 'cron_public_pages'); } /** - * Cron handler for redirecting pages. + * Cron handler + * + * @param array $page Pages * - * @param unknown_type $page + * @return bool + * @throws CronException + * @access private */ function cron_page_handler($page) { - global $CONFIG; - - if ($page[0]) { - switch (strtolower($page[0])) { - case 'minute' : - case 'fiveminute' : - case 'fifteenmin' : - case 'halfhour' : - case 'hourly' : - case 'daily' : - case 'weekly' : - case 'monthly': - case 'yearly' : - case 'reboot' : - set_input('period', $page[0]); - break; - default : - throw new CronException(sprintf(elgg_echo('CronException:unknownperiod'), $page[0])); - } - - // Include cron handler - include($CONFIG->path . "engine/handlers/cron_handler.php"); - } else { + if (!isset($page[0])) { forward(); } + + $period = strtolower($page[0]); + + $allowed_periods = array( + 'minute', 'fiveminute', 'fifteenmin', 'halfhour', 'hourly', + 'daily', 'weekly', 'monthly', 'yearly', 'reboot' + ); + + if (!in_array($period, $allowed_periods)) { + throw new CronException(elgg_echo('CronException:unknownperiod', array($period))); + } + + // Get a list of parameters + $params = array(); + $params['time'] = time(); + + // Data to return to + $old_stdout = ""; + ob_start(); + + $old_stdout = elgg_trigger_plugin_hook('cron', $period, $params, $old_stdout); + $std_out = ob_get_clean(); + + echo $std_out . $old_stdout; + return true; +} + +/** + * Register cron's pages as public in case we're in Walled Garden mode + * + * @param string $hook public_pages + * @param string $type system + * @param array $return_value Array of pages to allow + * @param mixed $params Params + * + * @return array + * @access private + */ +function cron_public_pages($hook, $type, $return_value, $params) { + $return_value[] = 'cron/minute'; + $return_value[] = 'cron/fiveminute'; + $return_value[] = 'cron/fifteenmin'; + $return_value[] = 'cron/halfhour'; + $return_value[] = 'cron/hourly'; + $return_value[] = 'cron/daily'; + $return_value[] = 'cron/weekly'; + $return_value[] = 'cron/monthly'; + $return_value[] = 'cron/yearly'; + $return_value[] = 'cron/reboot'; + + return $return_value; } -// Register a startup event -register_elgg_event_handler('init','system','cron_init');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'cron_init'); diff --git a/engine/lib/database.php b/engine/lib/database.php index ec703992d..a7949788d 100644 --- a/engine/lib/database.php +++ b/engine/lib/database.php @@ -1,35 +1,94 @@ <?php /** - * Elgg database - * Contains database connection and transfer functionality + * Elgg database procedural code. * - * @package Elgg - * @subpackage Core - - * @author Curverider Ltd + * Includes functions for establishing and retrieving a database link, + * reading data, writing data, upgrading DB schemas, and sanitizing input. + * + * @package Elgg.Core + * @subpackage Database + */ - * @link http://elgg.org/ +/** + * Query cache for all queries. + * + * Each query and its results are stored in this cache as: + * <code> + * $DB_QUERY_CACHE[query hash] => array(result1, result2, ... resultN) + * </code> + * @see elgg_query_runner() for details on the hash. + * + * @warning Elgg used to set this as an empty array to turn off the cache + * + * @global ElggLRUCache|null $DB_QUERY_CACHE + * @access private */ +global $DB_QUERY_CACHE; +$DB_QUERY_CACHE = null; -$DB_QUERY_CACHE = array(); +/** + * Queries to be executed upon shutdown. + * + * These queries are saved to an array and executed using + * a function registered by register_shutdown_function(). + * + * Queries are saved as an array in the format: + * <code> + * $DB_DELAYED_QUERIES[] = array( + * 'q' => str $query, + * 'l' => resource $dblink, + * 'h' => str $handler // a callback function + * ); + * </code> + * + * @global array $DB_DELAYED_QUERIES + * @access private + */ +global $DB_DELAYED_QUERIES; $DB_DELAYED_QUERIES = array(); /** + * Database connection resources. + * + * Each database link created with establish_db_link($name) is stored in + * $dblink as $dblink[$name] => resource. Use get_db_link($name) to retrieve it. + * + * @global resource[] $dblink + * @access private + */ +global $dblink; +$dblink = array(); + +/** + * Database call count + * + * Each call to the database increments this counter. + * + * @global integer $dbcalls + * @access private + */ +global $dbcalls; +$dbcalls = 0; + +/** + * Establish a connection to the database servser + * * Connect to the database server and use the Elgg database for a particular database link * - * @param string $dblinkname Default "readwrite"; you can change this to set up additional global database links, eg "read" and "write" + * @param string $dblinkname The type of database connection. Used to identify the + * resource. eg "read", "write", or "readwrite". + * + * @return void + * @throws DatabaseException + * @access private */ function establish_db_link($dblinkname = "readwrite") { // Get configuration, and globalise database link - global $CONFIG, $dblink, $DB_QUERY_CACHE, $dbcalls; - - if (!isset($dblink)) { - $dblink = array(); - } + global $CONFIG, $dblink, $DB_QUERY_CACHE; if ($dblinkname != "readwrite" && isset($CONFIG->db[$dblinkname])) { if (is_array($CONFIG->db[$dblinkname])) { - $index = rand(0,sizeof($CONFIG->db[$dblinkname])); + $index = rand(0, sizeof($CONFIG->db[$dblinkname])); $dbhost = $CONFIG->db[$dblinkname][$index]->dbhost; $dbuser = $CONFIG->db[$dblinkname][$index]->dbuser; $dbpass = $CONFIG->db[$dblinkname][$index]->dbpass; @@ -48,13 +107,14 @@ function establish_db_link($dblinkname = "readwrite") { } // Connect to database - if (!$dblink[$dblinkname] = mysql_connect($CONFIG->dbhost, $CONFIG->dbuser, $CONFIG->dbpass, true)) { - $msg = sprintf(elgg_echo('DatabaseException:WrongCredentials'), - $CONFIG->dbuser, $CONFIG->dbhost, "****"); + if (!$dblink[$dblinkname] = mysql_connect($dbhost, $dbuser, $dbpass, true)) { + $msg = elgg_echo('DatabaseException:WrongCredentials', + array($dbuser, $dbhost, "****")); throw new DatabaseException($msg); } - if (!mysql_select_db($CONFIG->dbname, $dblink[$dblinkname])) { - $msg = sprintf(elgg_echo('DatabaseException:NoConnect'), $CONFIG->dbname); + + if (!mysql_select_db($dbname, $dblink[$dblinkname])) { + $msg = elgg_echo('DatabaseException:NoConnect', array($dbname)); throw new DatabaseException($msg); } @@ -68,21 +128,22 @@ function establish_db_link($dblinkname = "readwrite") { // Set up cache if global not initialized and query cache not turned off if ((!$DB_QUERY_CACHE) && (!$db_cache_off)) { - $DB_QUERY_CACHE = new ElggStaticVariableCache('db_query_cache'); //array(); - //$DB_QUERY_CACHE = select_default_memcache('db_query_cache'); //array(); + // @todo if we keep this cache in 1.9, expose the size as a config parameter + $DB_QUERY_CACHE = new ElggLRUCache(200); } } /** - * Establish all database connections + * Establish database connections * * If the configuration has been set up for multiple read/write databases, set those - * links up separately; otherwise just create the one database link + * links up separately; otherwise just create the one database link. * + * @return void + * @access private */ function setup_db_connections() { - // Get configuration and globalise database link - global $CONFIG, $dblink; + global $CONFIG; if (!empty($CONFIG->db->split)) { establish_db_link('read'); @@ -93,53 +154,60 @@ function setup_db_connections() { } /** - * Shutdown hook to display profiling information about db (debug mode) + * Display profiling information about db at NOTICE debug level upon shutdown. + * + * @return void + * @access private */ function db_profiling_shutdown_hook() { global $dbcalls; - elgg_log("DB Queries for this page: $dbcalls", 'DEBUG'); + // demoted to NOTICE as it corrupts javasript at DEBUG + elgg_log("DB Queries for this page: $dbcalls", 'NOTICE'); } /** - * Execute any delayed queries. + * Execute any delayed queries upon shutdown. + * + * @return void + * @access private */ function db_delayedexecution_shutdown_hook() { - global $DB_DELAYED_QUERIES, $CONFIG; + global $DB_DELAYED_QUERIES; foreach ($DB_DELAYED_QUERIES as $query_details) { - // use one of our db functions so it is included in profiling. - $result = execute_query($query_details['q'], $query_details['l']); - try { + $link = $query_details['l']; + + if ($link == 'read' || $link == 'write') { + $link = get_db_link($link); + } elseif (!is_resource($link)) { + elgg_log("Link for delayed query not valid resource or db_link type. Query: {$query_details['q']}", 'WARNING'); + } + + $result = execute_query($query_details['q'], $link); + if ((isset($query_details['h'])) && (is_callable($query_details['h']))) { $query_details['h']($result); } - } catch (Exception $e) { // Suppress all errors since these can't be delt with here + } catch (Exception $e) { + // Suppress all errors since these can't be dealt with here elgg_log($e, 'WARNING'); } } } /** - * Alias to setup_db_connections, for use in the event handler + * Returns (if required, also creates) a database link resource. * - * @param string $event The event type - * @param string $object_type The object type - * @param mixed $object Used for nothing in this context - */ -function init_db($event, $object_type, $object = null) { - register_shutdown_function('db_delayedexecution_shutdown_hook'); - register_shutdown_function('db_profiling_shutdown_hook'); - // [Marcus Povey 20090213: Db connection moved to first db connection attempt] - return true; -} - -/** - * Gets the appropriate db link for the operation mode requested + * Database link resources are stored in the {@link $dblink} global. These + * resources are created by {@link setup_db_connections()}, which is called if + * no links exist. * - * @param string $dblinktype The type of link we want - "read", "write" or "readwrite" (the default) - * @return object Database link + * @param string $dblinktype The type of link we want: "read", "write" or "readwrite". + * + * @return resource Database link + * @access private */ function get_db_link($dblinktype) { global $dblink; @@ -148,40 +216,59 @@ function get_db_link($dblinktype) { return $dblink[$dblinktype]; } else if (isset($dblink['readwrite'])) { return $dblink['readwrite']; - } - else { + } else { setup_db_connections(); return get_db_link($dblinktype); } } /** - * Explain a given query, useful for debug. + * Execute an EXPLAIN for $query. + * + * @param string $query The query to explain + * @param mixed $link The database link resource to user. + * + * @return mixed An object of the query's result, or FALSE + * @access private */ function explain_query($query, $link) { if ($result = execute_query("explain " . $query, $link)) { return mysql_fetch_object($result); } - return false; + return FALSE; } /** * Execute a query. * - * @param string $query The query - * @param link $dblink the DB link - * @return Returns a the result of mysql_query + * $query is executed via {@link mysql_query()}. If there is an SQL error, + * a {@link DatabaseException} is thrown. + * + * @internal + * {@link $dbcalls} is incremented and the query is saved into the {@link $DB_QUERY_CACHE}. + * + * @param string $query The query + * @param resource $dblink The DB link + * + * @return resource result of mysql_query() + * @throws DatabaseException + * @access private */ function execute_query($query, $dblink) { - global $CONFIG, $dbcalls, $DB_QUERY_CACHE; + global $dbcalls; + + if ($query == NULL) { + throw new DatabaseException(elgg_echo('DatabaseException:InvalidQuery')); + } + + if (!is_resource($dblink)) { + throw new DatabaseException(elgg_echo('DatabaseException:InvalidDBLink')); + } $dbcalls++; $result = mysql_query($query, $dblink); - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE[$query] = -1; // Set initial cache to -1 - } if (mysql_errno($dblink)) { throw new DatabaseException(mysql_error($dblink) . "\n\n QUERY: " . $query); @@ -191,14 +278,17 @@ function execute_query($query, $dblink) { } /** - * Queue a query for execution after all output has been sent to the user. + * Queue a query for execution upon shutdown. * * You can specify a handler function if you care about the result. This function will accept - * the raw result from mysql_query(); + * the raw result from {@link mysql_query()}. + * + * @param string $query The query to execute + * @param resource|string $dblink The database link to use or the link type (read | write) + * @param string $handler A callback function to pass the results array to * - * @param string $query The query to execute - * @param resource $dblink The database link to use - * @param string $handler The handler + * @return true + * @access private */ function execute_delayed_query($query, $dblink, $handler = "") { global $DB_DELAYED_QUERIES; @@ -207,6 +297,10 @@ function execute_delayed_query($query, $dblink, $handler = "") { $DB_DELAYED_QUERIES = array(); } + if (!is_resource($dblink) && $dblink != 'read' && $dblink != 'write') { + return false; + } + // Construct delayed query $delayed_query = array(); $delayed_query['q'] = $query; @@ -215,212 +309,243 @@ function execute_delayed_query($query, $dblink, $handler = "") { $DB_DELAYED_QUERIES[] = $delayed_query; - return true; + return TRUE; } /** * Write wrapper for execute_delayed_query() * - * @param string $query The query to execute + * @param string $query The query to execute * @param string $handler The handler if you care about the result. + * + * @return true + * @uses execute_delayed_query() + * @uses get_db_link() + * @access private */ function execute_delayed_write_query($query, $handler = "") { - return execute_delayed_query($query, get_db_link('write'), $handler); + return execute_delayed_query($query, 'write', $handler); } /** * Read wrapper for execute_delayed_query() * - * @param string $query The query to execute + * @param string $query The query to execute * @param string $handler The handler if you care about the result. + * + * @return true + * @uses execute_delayed_query() + * @uses get_db_link() + * @access private */ function execute_delayed_read_query($query, $handler = "") { - return execute_delayed_query($query, get_db_link('read'), $handler); + return execute_delayed_query($query, 'read', $handler); } /** - * Use this function to get data from the database - * @param mixed $query The query being passed. - * @param string $call Optionally, the name of a function to call back to on each row (which takes $row as a single parameter) - * @return array An array of database result objects + * Retrieve rows from the database. + * + * Queries are executed with {@link execute_query()} and results + * are retrieved with {@link mysql_fetch_object()}. If a callback + * function $callback is defined, each row will be passed as the single + * argument to $callback. If no callback function is defined, the + * entire result set is returned as an array. + * + * @param mixed $query The query being passed. + * @param string $callback Optionally, the name of a function to call back to on each row + * + * @return array An array of database result objects or callback function results. If the query + * returned nothing, an empty array. + * @access private */ function get_data($query, $callback = "") { - global $CONFIG, $DB_QUERY_CACHE; + return elgg_query_runner($query, $callback, false); +} - // Is cached? - if ($DB_QUERY_CACHE) { - $cached_query = $DB_QUERY_CACHE[$query]; - } +/** + * Retrieve a single row from the database. + * + * Similar to {@link get_data()} but returns only the first row + * matched. If a callback function $callback is specified, the row will be passed + * as the only argument to $callback. + * + * @param mixed $query The query to execute. + * @param string $callback A callback function + * + * @return mixed A single database result object or the result of the callback function. + * @access private + */ +function get_data_row($query, $callback = "") { + return elgg_query_runner($query, $callback, true); +} + +/** + * Handles returning data from a query, running it through a callback function, + * and caching the results. This is for R queries (from CRUD). + * + * @access private + * + * @param string $query The query to execute + * @param string $callback An optional callback function to run on each row + * @param bool $single Return only a single result? + * + * @return array An array of database result objects or callback function results. If the query + * returned nothing, an empty array. + * @since 1.8.0 + * @access private + */ +function elgg_query_runner($query, $callback = null, $single = false) { + global $DB_QUERY_CACHE; - if ((isset($cached_query)) && ($cached_query)) { - elgg_log("$query results returned from cache"); + // Since we want to cache results of running the callback, we need to + // need to namespace the query with the callback and single result request. + // https://github.com/elgg/elgg/issues/4049 + $hash = (string)$callback . (int)$single . $query; - if ($cached_query === -1) { - // Last time this query returned nothing, so return an empty array - return array(); + // Is cached? + if ($DB_QUERY_CACHE) { + if (isset($DB_QUERY_CACHE[$hash])) { + elgg_log("DB query $query results returned from cache (hash: $hash)", 'NOTICE'); + return $DB_QUERY_CACHE[$hash]; } - - return $cached_query; } $dblink = get_db_link('read'); - $resultarray = array(); + $return = array(); if ($result = execute_query("$query", $dblink)) { + + // test for callback once instead of on each iteration. + // @todo check profiling to see if this needs to be broken out into + // explicit cases instead of checking in the iteration. + $is_callable = is_callable($callback); while ($row = mysql_fetch_object($result)) { - if (!empty($callback) && is_callable($callback)) { + if ($is_callable) { $row = $callback($row); } - if ($row) { - $resultarray[] = $row; + + if ($single) { + $return = $row; + break; + } else { + $return[] = $row; } } } - if (empty($resultarray)) { - elgg_log("DB query \"$query\" returned no results."); - return false; + if (empty($return)) { + elgg_log("DB query $query returned no results.", 'NOTICE'); } // Cache result if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE[$query] = $resultarray; - elgg_log("$query results cached"); - } - - return $resultarray; -} - -/** - * Use this function to get a single data row from the database - * @param mixed $query The query to run. - * @return object A single database result object - */ - -function get_data_row($query, $callback = "") { - global $CONFIG, $DB_QUERY_CACHE; - - // Is cached - if ($DB_QUERY_CACHE) { - $cached_query = $DB_QUERY_CACHE[$query]; - } - - if ((isset($cached_query)) && ($cached_query)) { - elgg_log("$query results returned from cache"); - - if ($cached_query === -1) { - // Last time this query returned nothing, so return false - //@todo fix me this should return array(). - return false; - } - - return $cached_query; - } - - $dblink = get_db_link('read'); - - if ($result = execute_query("$query", $dblink)) { - $row = mysql_fetch_object($result); - - // Cache result (even if query returned no data) - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE[$query] = $row; - elgg_log("$query results cached"); - } - - if (!empty($callback) && is_callable($callback)) { - $row = $callback($row); - } - - if ($row) { - return $row; - } + $DB_QUERY_CACHE[$hash] = $return; + elgg_log("DB query $query results cached (hash: $hash)", 'NOTICE'); } - elgg_log("$query returned no results."); - return FALSE; + return $return; } /** - * Use this function to insert database data; returns id or false + * Insert a row into the database. + * + * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. + * + * @param mixed $query The query to execute. * - * @param mixed $query The query to run. - * @return int $id the database id of the inserted row. + * @return int|false The database id of the inserted row if a AUTO_INCREMENT field is + * defined, 0 if not, and false on failure. + * @access private */ function insert_data($query) { - global $CONFIG, $DB_QUERY_CACHE; + elgg_log("DB query $query", 'NOTICE'); + $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - } - - elgg_log("Query cache invalidated"); + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return mysql_insert_id($dblink); } - return false; + return FALSE; } /** - * Update database data + * Update the database. + * + * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. + * + * @param string $query The query to run. * - * @param mixed $query The query to run. - * @return Bool on success + * @return bool + * @access private */ function update_data($query) { - global $CONFIG, $DB_QUERY_CACHE; + + elgg_log("DB query $query", 'NOTICE'); $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - elgg_log("Query cache invalidated"); - } + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { - // @todo why is this comment out? - //return mysql_affected_rows(); - return true; + return TRUE; } - return false; + return FALSE; } /** - * Use this function to delete data + * Remove data from the database. * - * @param mixed $query The SQL query to run - * @return int|false Either the number of affected rows, or false on failure + * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. + * + * @param string $query The SQL query to run + * + * @return int|false The number of affected rows or false on failure + * @access private */ function delete_data($query) { - global $CONFIG, $DB_QUERY_CACHE; + + elgg_log("DB query $query", 'NOTICE'); $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - elgg_log("Query cache invalidated"); - } + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return mysql_affected_rows($dblink); } - return false; + return FALSE; } +/** + * Invalidate the query cache + * + * @access private + */ +function _elgg_invalidate_query_cache() { + global $DB_QUERY_CACHE; + if ($DB_QUERY_CACHE instanceof ElggLRUCache) { + $DB_QUERY_CACHE->clear(); + elgg_log("Query cache invalidated", 'NOTICE'); + } elseif ($DB_QUERY_CACHE) { + // In case someone sets the cache to an array and primes it with data + $DB_QUERY_CACHE = array(); + elgg_log("Query cache invalidated", 'NOTICE'); + } +} /** - * Get the tables currently installed in the Elgg database + * Return tables matching the database prefix {@link $CONFIG->dbprefix}% in the currently + * selected database. * - * @return array List of tables + * @return array|false List of tables or false on failure + * @static array $tables Tables found matching the database prefix + * @access private */ function get_db_tables() { global $CONFIG; @@ -434,29 +559,36 @@ function get_db_tables() { $result = get_data("show tables like '" . $CONFIG->dbprefix . "%'"); } catch (DatabaseException $d) { // Likely we can't handle an exception here, so just return false. - return false; + return FALSE; } $tables = array(); if (is_array($result) && !empty($result)) { - foreach($result as $row) { + foreach ($result as $row) { $row = (array) $row; - if (is_array($row) && !empty($row)) - foreach($row as $element) { + if (is_array($row) && !empty($row)) { + foreach ($row as $element) { $tables[] = $element; } + } } } else { - return false; + return FALSE; } return $tables; } /** - * Run an optimize query on a mysql tables. Useful for executing after major data changes. + * Optimise a table. + * + * Executes an OPTIMIZE TABLE query on $table. Useful after large DB changes. * + * @param string $table The name of the table to optimise + * + * @return bool + * @access private */ function optimize_table($table) { $table = sanitise_string($table); @@ -466,18 +598,35 @@ function optimize_table($table) { /** * Get the last database error for a particular database link * - * @param database link $dblink + * @param resource $dblink The DB link + * * @return string Database error message + * @access private */ function get_db_error($dblink) { return mysql_error($dblink); } /** - * Runs a full database script from disk + * Runs a full database script from disk. + * + * The file specified should be a standard SQL file as created by + * mysqldump or similar. Statements must be terminated with ; + * and a newline character (\n or \r\n) with only one statement per line. + * + * The special string 'prefix_' is replaced with the database prefix + * as defined in {@link $CONFIG->dbprefix}. + * + * @warning Errors do not halt execution of the script. If a line + * generates an error, the error message is saved and the + * next line is executed. After the file is run, any errors + * are displayed as a {@link DatabaseException} * - * @uses $CONFIG * @param string $scriptlocation The full path to the script + * + * @return void + * @throws DatabaseException + * @access private */ function run_sql_script($scriptlocation) { if ($script = file_get_contents($scriptlocation)) { @@ -485,14 +634,18 @@ function run_sql_script($scriptlocation) { $errors = array(); + // Remove MySQL -- style comments $script = preg_replace('/\-\-.*\n/', '', $script); - $sql_statements = preg_split('/;[\n\r]+/', $script); - foreach($sql_statements as $statement) { + + // Statements must end with ; and a newline + $sql_statements = preg_split('/;[\n\r]+/', $script); + + foreach ($sql_statements as $statement) { $statement = trim($statement); - $statement = str_replace("prefix_",$CONFIG->dbprefix,$statement); + $statement = str_replace("prefix_", $CONFIG->dbprefix, $statement); if (!empty($statement)) { try { - $result = update_data($statement); + update_data($statement); } catch (DatabaseException $e) { $errors[] = $e->getMessage(); } @@ -500,99 +653,38 @@ function run_sql_script($scriptlocation) { } if (!empty($errors)) { $errortxt = ""; - foreach($errors as $error) + foreach ($errors as $error) { $errortxt .= " {$error};"; - throw new DatabaseException(elgg_echo('DatabaseException:DBSetupIssues') . $errortxt); - } - } else { - throw new DatabaseException(sprintf(elgg_echo('DatabaseException:ScriptNotFound'), $scriptlocation)); - } -} - -/** - * Upgrade the database schema in an ordered sequence. - * - * Makes use of schema upgrade files - * - * This is a about as core as it comes, so don't start running this from your plugins! - * - * @param int $version The version you are upgrading from (usually given in the Elgg version format of YYYYMMDDXX - see version.php for example) - * @param string $fromdir Optional directory to load upgrades from (default: engine/schema/upgrades/) - * @param bool $quiet If true, will suppress all error messages. Don't use this. - * @return bool - */ -function db_upgrade($version, $fromdir = "", $quiet = FALSE) { - global $CONFIG; - - // Elgg and its database must be installed to upgrade it! - if (!is_db_installed() || !is_installed()) { - return false; - } - - $version = (int) $version; - - if (!$fromdir) { - $fromdir = $CONFIG->path . 'engine/schema/upgrades/'; - } - - if ($handle = opendir($fromdir)) { - $sqlupgrades = array(); - - while ($sqlfile = readdir($handle)) { - if (!is_dir($fromdir . $sqlfile)) { - if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) { - $sql_version = (int) $matches[1]; - if ($sql_version > $version) { - $sqlupgrades[] = $sqlfile; - } - } } - } - - asort($sqlupgrades); - if (sizeof($sqlupgrades) > 0) { - foreach($sqlupgrades as $sqlfile) { - - // hide all errors. - if ($quiet) { - try { - run_sql_script($fromdir . $sqlfile); - } catch (DatabaseException $e) { - error_log($e->getmessage()); - } - } else { - run_sql_script($fromdir . $sqlfile); - } - } + $msg = elgg_echo('DatabaseException:DBSetupIssues') . $errortxt; + throw new DatabaseException($msg); } + } else { + $msg = elgg_echo('DatabaseException:ScriptNotFound', array($scriptlocation)); + throw new DatabaseException($msg); } - - return TRUE; } /** - * This function, called by validate_platform(), will check whether the installed version of - * MySQL meets the minimum required. + * Format a query string for logging * - * TODO: If multiple dbs are supported check which db is supported and use the appropriate code to validate - * the appropriate version. - * - * @return bool + * @param string $query Query string + * @return string + * @access private */ -function db_check_version() { - $version = mysql_get_server_info(); - $points = explode('.', $version); - - if ($points[0] < 5) { - return false; - } - - return true; +function elgg_format_query($query) { + // remove newlines and extra spaces so logs are easier to read + return preg_replace('/\s\s+/', ' ', $query); } /** * Sanitise a string for database use, but with the option of escaping extra characters. + * + * @param string $string The string to sanitise + * @param string $extra_escapeable Extra characters to escape with '\\' + * + * @return string The escaped string */ function sanitise_string_special($string, $extra_escapeable = '') { $string = sanitise_string($string); @@ -605,9 +697,10 @@ function sanitise_string_special($string, $extra_escapeable = '') { } /** - * Sanitise a string for database use + * Sanitise a string for database use. * * @param string $string The string to sanitise + * * @return string Sanitised string */ function sanitise_string($string) { @@ -620,34 +713,52 @@ function sanitise_string($string) { * Wrapper function for alternate English spelling * * @param string $string The string to sanitise + * * @return string Sanitised string - * @uses sanitise_string */ function sanitize_string($string) { return sanitise_string($string); } /** - * Sanitises an integer for database use + * Sanitises an integer for database use. * - * @param int $int - * @return int Sanitised integer + * @param int $int Value to be sanitized + * @param bool $signed Whether negative values should be allowed (true) + * @return int */ -function sanitise_int($int) { +function sanitise_int($int, $signed = true) { + $int = (int) $int; + + if ($signed === false) { + if ($int < 0) { + $int = 0; + } + } + return (int) $int; } /** - * Wrapper function for alternate English spelling + * Sanitizes an integer for database use. + * Wrapper function for alternate English spelling (@see sanitise_int) * - * @param int $int - * @return int Sanitised integer - * @uses sanitise_string + * @param int $int Value to be sanitized + * @param bool $signed Whether negative values should be allowed (true) + * @return int */ -function sanitize_int($int) { - return (int) $int; +function sanitize_int($int, $signed = true) { + return sanitise_int($int, $signed); } -// Stuff for initialisation +/** + * Registers shutdown functions for database profiling and delayed queries. + * + * @access private + */ +function init_db() { + register_shutdown_function('db_delayedexecution_shutdown_hook'); + register_shutdown_function('db_profiling_shutdown_hook'); +} -register_elgg_event_handler('boot','system','init_db',0);
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'init_db'); diff --git a/engine/lib/deprecated-1.7.php b/engine/lib/deprecated-1.7.php new file mode 100644 index 000000000..ee95b5611 --- /dev/null +++ b/engine/lib/deprecated-1.7.php @@ -0,0 +1,1164 @@ +<?php +/** + * Get entities with the specified access collection id. + * + * @deprecated 1.7. Use elgg_get_entities_from_access_id() + * + * @param int $collection_id ID of collection + * @param string $entity_type Type of entities + * @param string $entity_subtype Subtype of entities + * @param int $owner_guid Guid of owner + * @param int $limit Limit of number of entities to return + * @param int $offset Skip this many entities + * @param string $order_by Column to order by + * @param int $site_guid The site guid + * @param bool $count Return a count or entities + * + * @return array + */ +function get_entities_from_access_id($collection_id, $entity_type = "", $entity_subtype = "", + $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { + // log deprecated warning + elgg_deprecated_notice('get_entities_from_access_id() was deprecated by elgg_get_entities()', 1.7); + + if (!$collection_id) { + return FALSE; + } + + // build the options using given parameters + $options = array(); + $options['limit'] = $limit; + $options['offset'] = $offset; + $options['count'] = $count; + + if ($entity_type) { + $options['type'] = sanitise_string($entity_type); + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + if ($site_guid) { + $options['site_guid'] = $site_guid; + } + + if ($order_by) { + $options['order_by'] = sanitise_string("e.time_created, $order_by"); + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($site_guid) { + $options['site_guid'] = $site_guid; + } + + $options['access_id'] = $collection_id; + + return elgg_get_entities_from_access_id($options); +} + +/** + * @deprecated 1.7 + */ +function get_entities_from_access_collection($collection_id, $entity_type = "", $entity_subtype = "", + $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { + + elgg_deprecated_notice('get_entities_from_access_collection() was deprecated by elgg_get_entities()', 1.7); + + return get_entities_from_access_id($collection_id, $entity_type, $entity_subtype, + $owner_guid, $limit, $offset, $order_by, $site_guid, $count); +} + +/** + * Get entities from annotations + * + * No longer used. + * + * @deprecated 1.7 Use elgg_get_entities_from_annotations() + * + * @param mixed $entity_type Type of entity + * @param mixed $entity_subtype Subtype of entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param int $owner_guid Guid of owner of annotation + * @param int $group_guid Guid of group + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by SQL order by string + * @param bool $count Count or return entities + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * + * @return unknown_type + */ +function get_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", +$value = "", $owner_guid = 0, $group_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", +$count = false, $timelower = 0, $timeupper = 0) { + $msg = 'get_entities_from_annotations() is deprecated by elgg_get_entities_from_annotations().'; + elgg_deprecated_notice($msg, 1.7); + + $options = array(); + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($value) { + $options['annotation_values'] = $value; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['annotation_owner_guids'] = $owner_guid; + } else { + $options['annotation_owner_guid'] = $owner_guid; + } + } + + if ($group_guid) { + $options['container_guid'] = $group_guid; + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by'] = "maxtime $order_by"; + } + + if ($count) { + $options['count'] = $count; + } + + if ($timelower) { + $options['annotation_created_time_lower'] = $timelower; + } + + if ($timeupper) { + $options['annotation_created_time_upper'] = $timeupper; + } + + return elgg_get_entities_from_annotations($options); +} + +/** + * Lists entities + * + * @see elgg_view_entity_list + * + * @param string $entity_type Type of entity. + * @param string $entity_subtype Subtype of entity. + * @param string $name Name of annotation. + * @param string $value Value of annotation. + * @param int $limit Maximum number of results to return. + * @param int $owner_guid Owner. + * @param int $group_guid Group container. Currently only supported if entity_type is object + * @param boolean $asc Whether to list in ascending or descending order (default: desc) + * @param boolean $fullview Whether to display the entities in full + * @param boolean $listtypetoggle Can 'gallery' view can be displayed (default: no) + * + * @deprecated 1.7 Use elgg_list_entities_from_annotations() + * @return string Formatted entity list + */ +function list_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", +$value = "", $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, +$listtypetoggle = false) { + + $msg = 'list_entities_from_annotations is deprecated by elgg_list_entities_from_annotations'; + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + if ($name) { + $options['annotation_names'] = $name; + } + + if ($value) { + $options['annotation_values'] = $value; + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($owner_guid) { + $options['annotation_owner_guid'] = $owner_guid; + } + + if ($group_guid) { + $options['container_guid'] = $group_guid; + } + + if ($asc) { + $options['order_by'] = 'maxtime desc'; + } + + if ($offset = sanitise_int(get_input('offset', null))) { + $options['offset'] = $offset; + } + + $options['full_view'] = $fullview; + $options['list_type_toggle'] = $listtypetoggle; + $options['pagination'] = $pagination; + + return elgg_list_entities_from_annotations($options); +} + +/** + * Returns all php files in a directory. + * + * @deprecated 1.7 Use elgg_get_file_list() instead + * + * @param string $directory Directory to look in + * @param array $exceptions Array of extensions (with .!) to ignore + * @param array $list A list files to include in the return + * + * @return array + */ +function get_library_files($directory, $exceptions = array(), $list = array()) { + elgg_deprecated_notice('get_library_files() deprecated by elgg_get_file_list()', 1.7); + return elgg_get_file_list($directory, $exceptions, $list, array('.php')); +} + +/** + * Add action tokens to URL. + * + * @param string $url URL + * + * @return string + * + * @deprecated 1.7 final + */ +function elgg_validate_action_url($url) { + elgg_deprecated_notice('elgg_validate_action_url() deprecated by elgg_add_action_tokens_to_url().', + 1.7); + + return elgg_add_action_tokens_to_url($url); +} + +/** + * Does nothing. + * + * @deprecated 1.7 + * @return 0 + */ +function test_ip() { + elgg_deprecated_notice('test_ip() was removed because of licensing issues.', 1.7); + + return 0; +} + +/** + * Does nothing. + * + * @return bool + * @deprecated 1.7 + */ +function is_ip_in_array() { + elgg_deprecated_notice('is_ip_in_array() was removed because of licensing issues.', 1.7); + + return false; +} + +/** + * Returns entities. + * + * @deprecated 1.7. Use elgg_get_entities(). + * + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param string $order_by Order by clause + * @param int $limit Limit + * @param int $offset Offset + * @param bool $count Return a count or an array of entities + * @param int $site_guid Site GUID + * @param int $container_guid Container GUID + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * + * @return array + */ +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_deprecated_notice('get_entities() was deprecated by elgg_get_entities().', 1.7); + + // rewrite owner_guid to container_guid to emulate old functionality + if ($owner_guid != "") { + if (is_null($container_guid)) { + $container_guid = $owner_guid; + $owner_guid = NULL; + } + } + + $options = array(); + if ($type) { + if (is_array($type)) { + $options['types'] = $type; + } else { + $options['type'] = $type; + } + } + + if ($subtype) { + if (is_array($subtype)) { + $options['subtypes'] = $subtype; + } else { + $options['subtype'] = $subtype; + } + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($order_by) { + $options['order_by'] = $order_by; + } + + // need to pass 0 for all option + $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['created_time_upper'] = $timeupper; + } + + if ($timelower) { + $options['created_time_lower'] = $timelower; + } + + $r = elgg_get_entities($options); + return $r; +} + +/** + * Delete multiple entities that match a given query. + * This function iterates through and calls delete_entity on + * each one, this is somewhat inefficient but lets + * the 'delete' event 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 + * + * @return false + */ +function delete_entities($type = "", $subtype = "", $owner_guid = 0) { + elgg_deprecated_notice('delete_entities() was deprecated because no one should use it.', 1.7); + return false; +} + +/** + * Lists entities. + * + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param bool $fullview Show entity full views + * @param bool $listtypetoggle Show list type toggle + * @param bool $allowedtypes A string of the allowed types + * + * @return string + * @deprecated 1.7. Use elgg_list_registered_entities(). + */ +function list_registered_entities($owner_guid = 0, $limit = 10, $fullview = true, +$listtypetoggle = false, $allowedtypes = true) { + + elgg_deprecated_notice('list_registered_entities() was deprecated by elgg_list_registered_entities().', 1.7); + + $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; + } + + // need to send because might be BOOL + $options['full_view'] = $fullview; + $options['list_type_toggle'] = $listtypetoggle; + + $options['offset'] = get_input('offset', 0); + + return elgg_list_registered_entities($options); +} + +/** + * Lists entities + * + * @deprecated 1.7. Use elgg_list_entities(). + * + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param bool $fullview Display entity full views? + * @param bool $listtypetoggle Allow switching to gallery mode? + * @param bool $pagination Show pagination? + * + * @return string + */ +function list_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, +$listtypetoggle = false, $pagination = true) { + + elgg_deprecated_notice('list_entities() was deprecated by elgg_list_entities()!', 1.7); + + $options = array(); + + // rewrite owner_guid to container_guid to emulate old functionality + if ($owner_guid) { + $options['container_guids'] = $owner_guid; + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset = sanitise_int(get_input('offset', null))) { + $options['offset'] = $offset; + } + + $options['full_view'] = $fullview; + $options['list_type_toggle'] = $listtypetoggle; + $options['pagination'] = $pagination; + + return elgg_list_entities($options); +} + +/** + * Searches for a group based on a complete or partial name or description + * + * @param string $criteria The partial or full name or description + * @param int $limit Limit of the search. + * @param int $offset Offset. + * @param string $order_by The order. + * @param boolean $count Whether to return the count of results or just the results. + * + * @return mixed + * @deprecated 1.7 + */ +function search_for_group($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { + elgg_deprecated_notice('search_for_group() was deprecated by new search plugin.', 1.7); + global $CONFIG; + + $criteria = sanitise_string($criteria); + $limit = (int)$limit; + $offset = (int)$offset; + $order_by = sanitise_string($order_by); + + $access = get_access_sql_suffix("e"); + + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT e.* "; + } + $query .= "from {$CONFIG->dbprefix}entities e" + . " JOIN {$CONFIG->dbprefix}groups_entity g on e.guid=g.guid where "; + + $query .= "(g.name like \"%{$criteria}%\" or g.description like \"%{$criteria}%\")"; + $query .= " and $access"; + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Returns a formatted list of groups suitable for injecting into search. + * + * @deprecated 1.7 + * + * @param string $hook Hook name + * @param string $user User + * @param mixed $returnvalue Previous hook's return value + * @param string $tag Tag to search on + * + * @return string + */ +function search_list_groups_by_name($hook, $user, $returnvalue, $tag) { + elgg_deprecated_notice('search_list_groups_by_name() was deprecated by new search plugin', 1.7); + // Change this to set the number of groups that display on the search page + $threshold = 4; + + $object = get_input('object'); + + if (!get_input('offset') && (empty($object) || $object == 'group')) { + if ($groups = search_for_group($tag, $threshold)) { + $countgroups = search_for_group($tag, 0, 0, "", true); + + $return = elgg_view('group/search/startblurb', array('count' => $countgroups, 'tag' => $tag)); + foreach ($groups as $group) { + $return .= elgg_view_entity($group); + } + $vars = array('count' => $countgroups, 'threshold' => $threshold, 'tag' => $tag); + $return .= elgg_view('group/search/finishblurb', $vars); + return $return; + } + } +} + +/** + * Displays a list of group objects that have been searched for. + * + * @see elgg_view_entity_list + * + * @param string $tag Search criteria + * @param int $limit The number of entities to display on a page + * + * @return string The list in a form suitable to display + * @deprecated 1.7 + */ +function list_group_search($tag, $limit = 10) { + elgg_deprecated_notice('list_group_search() was deprecated by new search plugin.', 1.7); + $offset = (int) get_input('offset'); + $limit = (int) $limit; + $count = (int) search_for_group($tag, 10, 0, '', true); + $entities = search_for_group($tag, $limit, $offset); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); + +} + +/** + * Return a list of entities based on the given search criteria. + * + * @deprecated 1.7 use elgg_get_entities_from_metadata(). + * + * @param mixed $meta_name Metadat name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any. + * @param bool $count Return a count instead of entities + * @param bool $case_sensitive Metadata names case sensitivity + * + * @return int|array A list of entities, or a count if $count is set to true + */ +function get_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", +$entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", +$site_guid = 0, $count = FALSE, $case_sensitive = TRUE) { + + elgg_deprecated_notice('get_entities_from_metadata() was deprecated by elgg_get_entities_from_metadata()!', 1.7); + + $options = array(); + + $options['metadata_names'] = $meta_name; + + if ($meta_value) { + $options['metadata_values'] = $meta_value; + } + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + // need to be able to pass false + $options['metadata_case_sensitive'] = $case_sensitive; + + return elgg_get_entities_from_metadata($options); +} + +/** + * Return entities from metadata + * + * @deprecated 1.7. Use elgg_get_entities_from_metadata(). + * + * @param mixed $meta_array Metadata name + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any. + * @param bool $count Return a count instead of entities + * @param bool $meta_array_operator Operator for metadata values + * + * @return int|array A list of entities, or a count if $count is set to true + */ +function get_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", +$owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, +$count = false, $meta_array_operator = 'and') { + + elgg_deprecated_notice('get_entities_from_metadata_multi() was deprecated by elgg_get_entities_from_metadata()!', 1.7); + + if (!is_array($meta_array) || sizeof($meta_array) == 0) { + return false; + } + + $options = array(); + + $options['metadata_name_value_pairs'] = $meta_array; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + $options['metadata_name_value_pairs_operator'] = $meta_array_operator; + + return elgg_get_entities_from_metadata($options); +} + +/** + * Returns a menu item for use in the children section of add_menu() + * This is not currently used in the Elgg core. + * + * @param string $menu_name The name of the menu item + * @param string $menu_url Its URL + * + * @return stdClass|false Depending on success + * @deprecated 1.7 + */ +function menu_item($menu_name, $menu_url) { + elgg_deprecated_notice('menu_item() is deprecated by add_submenu_item', 1.7); + return make_register_object($menu_name, $menu_url); +} + +/** + * Searches for an object based on a complete or partial title + * or description using full text searching. + * + * IMPORTANT NOTE: With MySQL's default setup: + * 1) $criteria must be 4 or more characters long + * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! + * + * @param string $criteria The partial or full name or username. + * @param int $limit Limit of the search. + * @param int $offset Offset. + * @param string $order_by The order. + * @param boolean $count Whether to return the count of results or just the results. + * + * @return int|false + * @deprecated 1.7 + */ +function search_for_object($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { + elgg_deprecated_notice('search_for_object() was deprecated by new search plugin.', 1.7); + global $CONFIG; + + $criteria = sanitise_string($criteria); + $limit = (int)$limit; + $offset = (int)$offset; + $order_by = sanitise_string($order_by); + $container_guid = (int)$container_guid; + + $access = get_access_sql_suffix("e"); + + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT e.* "; + } + $query .= "from {$CONFIG->dbprefix}entities e + join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid + where match(o.title,o.description) against ('$criteria') and $access"; + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Returns a formatted list of objects suitable for injecting into search. + * + * @deprecated 1.7 + * + * @param sting $hook Hook + * @param string $user user + * @param mixed $returnvalue Previous return value + * @param mixed $tag Search term + * + * @return array + */ +function search_list_objects_by_name($hook, $user, $returnvalue, $tag) { + elgg_deprecated_notice('search_list_objects_by_name was deprecated by new search plugin.', 1.7); + + // Change this to set the number of users that display on the search page + $threshold = 4; + + $object = get_input('object'); + + if (!get_input('offset') && (empty($object) || $object == 'user')) { + if ($users = search_for_user($tag, $threshold)) { + $countusers = search_for_user($tag, 0, 0, "", true); + + $return = elgg_view('user/search/startblurb', array('count' => $countusers, 'tag' => $tag)); + foreach ($users as $user) { + $return .= elgg_view_entity($user); + } + $return .= elgg_view('user/search/finishblurb', + array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag)); + + return $return; + + } + } +} + +/** + * Return entities from relationships + * + * @deprecated 1.7 Use elgg_get_entities_from_relationship() + * + * @param string $relationship The relationship type + * @param int $relationship_guid The GUID of the relationship owner + * @param bool $inverse_relationship Invert relationship? + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Entity owner GUID + * @param string $order_by Order by clause + * @param int $limit Limit + * @param int $offset Offset + * @param bool $count Return a count instead of entities? + * @param int $site_guid Site GUID + * + * @return mixed + */ +function get_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + + elgg_deprecated_notice('get_entities_from_relationship() was deprecated by elgg_get_entities_from_relationship()!', 1.7); + + $options = array(); + + $options['relationship'] = $relationship; + $options['relationship_guid'] = $relationship_guid; + $options['inverse_relationship'] = $inverse_relationship; + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_relationship($options); +} + +/** + * Searches for a site based on a complete or partial name + * or description or url using full text searching. + * + * IMPORTANT NOTE: With MySQL's default setup: + * 1) $criteria must be 4 or more characters long + * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! + * + * @param string $criteria The partial or full name or username. + * @param int $limit Limit of the search. + * @param int $offset Offset. + * @param string $order_by The order. + * @param boolean $count Whether to return the count of results or just the results. + * + * @return mixed + * @deprecated 1.7 + */ +function search_for_site($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { + elgg_deprecated_notice('search_for_site() was deprecated by new search plugin.', 1.7); + global $CONFIG; + + $criteria = sanitise_string($criteria); + $limit = (int)$limit; + $offset = (int)$offset; + $order_by = sanitise_string($order_by); + + $access = get_access_sql_suffix("e"); + + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT e.* "; + } + $query .= "from {$CONFIG->dbprefix}entities e + join {$CONFIG->dbprefix}sites_entity s on e.guid=s.guid + where match(s.name, s.description, s.url) against ('$criteria') and $access"; + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Searches for a user based on a complete or partial name or username. + * + * @param string $criteria The partial or full name or username. + * @param int $limit Limit of the search. + * @param int $offset Offset. + * @param string $order_by The order. + * @param boolean $count Whether to return the count of results or just the results. + * + * @return mixed + * @deprecated 1.7 + */ +function search_for_user($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { + elgg_deprecated_notice('search_for_user() was deprecated by new search.', 1.7); + global $CONFIG; + + $criteria = sanitise_string($criteria); + $limit = (int)$limit; + $offset = (int)$offset; + $order_by = sanitise_string($order_by); + + $access = get_access_sql_suffix("e"); + + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT e.* "; + } + $query .= "from {$CONFIG->dbprefix}entities e + join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid where "; + + $query .= "(u.name like \"%{$criteria}%\" or u.username like \"%{$criteria}%\")"; + $query .= " and $access"; + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Displays a list of user objects that have been searched for. + * + * @see elgg_view_entity_list + * + * @param string $tag Search criteria + * @param int $limit The number of entities to display on a page + * + * @return string The list in a form suitable to display + * + * @deprecated 1.7 + */ +function list_user_search($tag, $limit = 10) { + elgg_deprecated_notice('list_user_search() deprecated by new search', 1.7); + $offset = (int) get_input('offset'); + $limit = (int) $limit; + $count = (int) search_for_user($tag, 10, 0, '', true); + $entities = search_for_user($tag, $limit, $offset); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); +} + +/** + * Returns a formatted list of users suitable for injecting into search. + * + * @deprecated 1.7 + * + * @param string $hook Hook name + * @param string $user User? + * @param mixed $returnvalue Previous hook's return value + * @param mixed $tag Tag to search against + * + * @return void + */ +function search_list_users_by_name($hook, $user, $returnvalue, $tag) { + elgg_deprecated_notice('search_list_users_by_name() was deprecated by new search', 1.7); + // Change this to set the number of users that display on the search page + $threshold = 4; + + $object = get_input('object'); + + if (!get_input('offset') && (empty($object) || $object == 'user')) { + if ($users = search_for_user($tag, $threshold)) { + $countusers = search_for_user($tag, 0, 0, "", true); + + $return = elgg_view('user/search/startblurb', array('count' => $countusers, 'tag' => $tag)); + foreach ($users as $user) { + $return .= elgg_view_entity($user); + } + + $vars = array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag); + $return .= elgg_view('user/search/finishblurb', $vars); + return $return; + + } + } +} + +/** + * Extend a view + * + * @deprecated 1.7. Use elgg_extend_view(). + * + * @param string $view The view to extend. + * @param string $view_name This view is added to $view + * @param int $priority The priority, from 0 to 1000, + * to add at (lowest numbers displayed first) + * @param string $viewtype Not used + * + * @return void + */ +function extend_view($view, $view_name, $priority = 501, $viewtype = '') { + elgg_deprecated_notice('extend_view() was deprecated by elgg_extend_view()!', 1.7); + elgg_extend_view($view, $view_name, $priority, $viewtype); +} + +/** + * Get views in a dir + * + * @deprecated 1.7. Use elgg_get_views(). + * + * @param string $dir Dir + * @param string $base Base view + * + * @return array + */ +function get_views($dir, $base) { + elgg_deprecated_notice('get_views() was deprecated by elgg_get_views()!', 1.7); + elgg_get_views($dir, $base); +} + +/** + * Constructs and returns a register object. + * + * @param string $register_name The name of the register + * @param mixed $register_value The value of the register + * @param array $children_array Optionally, an array of children + * + * @return false|stdClass Depending on success + * @deprecated 1.7 Use {@link add_submenu_item()} + */ +function make_register_object($register_name, $register_value, $children_array = array()) { + elgg_deprecated_notice('make_register_object() is deprecated by add_submenu_item()', 1.7); + if (empty($register_name) || empty($register_value)) { + return false; + } + + $register = new stdClass; + $register->name = $register_name; + $register->value = $register_value; + $register->children = $children_array; + + return $register; +} + +/** + * THIS FUNCTION IS DEPRECATED. + * + * Delete a object's extra data. + * + * @todo - this should be removed - was deprecated in 1.5 or earlier + * + * @param int $guid GUID + * + * @return 1 + * @deprecated 1.7 + */ +function delete_object_entity($guid) { + system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); + + return 1; // Always return that we have deleted one row in order to not break existing code. +} + +/** + * THIS FUNCTION IS DEPRECATED. + * + * Delete a user's extra data. + * + * @todo remove + * + * @param int $guid User GUID + * + * @return 1 + * @deprecated 1.7 + */ +function delete_user_entity($guid) { + system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); + + return 1; // Always return that we have deleted one row in order to not break existing code. +}
\ No newline at end of file diff --git a/engine/lib/deprecated-1.8.php b/engine/lib/deprecated-1.8.php new file mode 100644 index 000000000..91068d047 --- /dev/null +++ b/engine/lib/deprecated-1.8.php @@ -0,0 +1,4820 @@ +<?php +/** + * *************************************************************************** + * NOTE: If this is ever removed from Elgg, sites lose the ability to upgrade + * from 1.7.x and earlier to the latest version of Elgg without upgrading to + * 1.8 first. + * *************************************************************************** + * + * Upgrade the database schema in an ordered sequence. + * + * Executes all upgrade files in elgg/engine/schema/upgrades/ in sequential order. + * Upgrade files must be in the standard Elgg release format of YYYYMMDDII.sql + * where II is an incrementor starting from 01. + * + * Files that are < $version will be ignored. + * + * @warning Plugin authors should not call this function directly. + * + * @param int $version The version you are upgrading from in the format YYYYMMDDII. + * @param string $fromdir Optional directory to load upgrades from. default: engine/schema/upgrades/ + * @param bool $quiet If true, suppress all error messages. Only use for the upgrade from <=1.6. + * + * @return int The number of upgrades run. + * @see upgrade.php + * @see version.php + * @deprecated 1.8 Use PHP upgrades for sql changes. + */ +function db_upgrade($version, $fromdir = "", $quiet = FALSE) { + global $CONFIG; + + elgg_deprecated_notice('db_upgrade() is deprecated by using PHP upgrades.', 1.8); + + $version = (int) $version; + + if (!$fromdir) { + $fromdir = $CONFIG->path . 'engine/schema/upgrades/'; + } + + $i = 0; + + if ($handle = opendir($fromdir)) { + $sqlupgrades = array(); + + while ($sqlfile = readdir($handle)) { + if (!is_dir($fromdir . $sqlfile)) { + if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) { + $sql_version = (int) $matches[1]; + if ($sql_version > $version) { + $sqlupgrades[] = $sqlfile; + } + } + } + } + + asort($sqlupgrades); + + if (sizeof($sqlupgrades) > 0) { + foreach ($sqlupgrades as $sqlfile) { + + // hide all errors. + if ($quiet) { + try { + run_sql_script($fromdir . $sqlfile); + } catch (DatabaseException $e) { + error_log($e->getmessage()); + } + } else { + run_sql_script($fromdir . $sqlfile); + } + $i++; + } + } + } + + return $i; +} + +/** + * Lists entities from an access collection + * + * @deprecated 1.8 Use elgg_list_entities_from_access_id() + * + * @return str + */ +function list_entities_from_access_id($access_id, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true) { + + elgg_deprecated_notice("All list_entities* functions were deprecated in 1.8. Use elgg_list_entities* instead.", 1.8); + + echo elgg_list_entities_from_access_id(array('access_id' => $access_id, + 'type' => $entity_type, 'subtype' => $entity_subtype, 'owner_guids' => $owner_guid, + 'limit' => $limit, 'full_view' => $fullview, 'list_type_toggle' => $listtypetoggle, + 'pagination' => $pagination,)); +} + +/** + * Registers a particular action in memory + * + * @deprecated 1.8 Use {@link elgg_register_action()} instead + * + * @param string $action The name of the action (eg "register", "account/settings/save") + * @param boolean $public Can this action be accessed by people not logged into the system? + * @param string $filename Optionally, the filename where this action is located + * @param boolean $admin_only Whether this action is only available to admin users. + */ +function register_action($action, $public = false, $filename = "", $admin_only = false) { + elgg_deprecated_notice("register_action() was deprecated by elgg_register_action()", 1.8); + + if ($admin_only) { + $access = 'admin'; + } elseif ($public) { + $access = 'public'; + } else { + $access = 'logged_in'; + } + + return elgg_register_action($action, $filename, $access); +} + +/** + * Register an admin page with the admin panel. + * This function extends the view "admin/main" with the provided view. + * This view should provide a description and either a control or a link to. + * + * @deprecated 1.8 Extend admin views manually + * + * Usage: + * - To add a control to the main admin panel then extend admin/main + * - To add a control to a new page create a page which renders a view admin/subpage + * (where subpage is your new page - + * nb. some pages already exist that you can extend), extend the main view to point to it, + * and add controls to your new view. + * + * At the moment this is essentially a wrapper around elgg_extend_view(). + * + * @param string $new_admin_view The view associated with the control you're adding + * @param string $view The view to extend, by default this is 'admin/main'. + * @param int $priority Optional priority to govern the appearance in the list. + * + * @return void + */ +function extend_elgg_admin_page($new_admin_view, $view = 'admin/main', $priority = 500) { + elgg_deprecated_notice('extend_elgg_admin_page() does nothing. Extend admin views manually.', 1.8); +} + +/** + * Get entities ordered by a mathematical calculation + * + * @deprecated 1.8 Use elgg_get_entities_from_annotation_calculation() + * + * @param string $sum What sort of calculation to perform + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $mdname Metadata name + * @param string $mdvalue Metadata value + * @param int $owner_guid GUID of owner of annotation + * @param int $limit Limit of results + * @param int $offset Offset of results + * @param string $orderdir Order of results + * @param bool $count Return count or entities + * + * @return mixed + */ +function get_entities_from_annotations_calculate_x($sum = "sum", $entity_type = "", $entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $owner_guid = 0, $limit = 10, $offset = 0, $orderdir = 'desc', $count = false) { + + $msg = 'get_entities_from_annotations_calculate_x() is deprecated by elgg_get_entities_from_annotation_calculation().'; + + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + $options['calculation'] = $sum; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($mdname) { + $options['metadata_names'] = $mdname; + } + + if ($mdvalue) { + $options['metadata_values'] = $mdvalue; + } + + // original function rewrote this to container guid. + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['container_guids'] = $owner_guid; + } else { + $options['container_guid'] = $owner_guid; + } + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + $options['order_by'] = "annotation_calculation $orderdir"; + + $options['count'] = $count; + + return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Returns entities ordered by the sum of an annotation + * + * @warning This is function uses sum instead of count. THIS IS SLOW. See #3366. + * This should be used when you have annotations with different values and you + * want a list of entities ordered by the sum of all of those values. + * If you want a list of entities ordered by the number of annotations on each entity, + * use __get_entities_from_annotations_calculate_x() and pass 'count' as the first param. + * + * @deprecated 1.8 Use elgg_get_entities_from_annotation_calculation() + * + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $mdname Metadata name + * @param string $mdvalue Metadata value + * @param int $owner_guid GUID of owner of annotation + * @param int $limit Limit of results + * @param int $offset Offset of results + * @param string $orderdir Order of results + * @param bool $count Return count or entities + * + * @return unknown + */ +function get_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $owner_guid = 0, $limit = 10, $offset = 0, $orderdir = 'desc', $count = false) { + + $msg = 'get_entities_from_annotation_count() is deprecated by elgg_get_entities_from_annotation_calculation().'; + + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + $options['calculation'] = 'sum'; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($mdname) { + $options['metadata_names'] = $mdname; + } + + if ($mdvalue) { + $options['metadata_values'] = $mdvalue; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + $options['order_by'] = "annotation_calculation $orderdir"; + + $options['count'] = $count; + + return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Lists entities by the totals of a particular kind of annotation + * + * @deprecated 1.8 Use elgg_list_entities_from_annotation_calculation() + * + * @param string $entity_type Type of entity. + * @param string $entity_subtype Subtype of entity. + * @param string $name Name of annotation. + * @param int $limit Maximum number of results to return. + * @param int $owner_guid Owner. + * @param int $group_guid Group container. Currently only supported if entity_type is object + * @param boolean $asc Whether to list in ascending or descending order (default: desc) + * @param boolean $fullview Whether to display the entities in full + * @param boolean $listtypetoggle Can the 'gallery' view can be displayed (default: no) + * @param boolean $pagination Add pagination + * @param string $orderdir Order desc or asc + * + * @return string Formatted entity list + */ +function list_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, $listtypetoggle = false, $pagination = true, $orderdir = 'desc') { + + $msg = 'list_entities_from_annotation_count() is deprecated by elgg_list_entities_from_annotation_calculation().'; + + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + $options['calculation'] = 'sum'; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + $options['full_view'] = $fullview; + + $options['list_type_toggle'] = $listtypetoggle; + + $options['pagination'] = $pagination; + + $options['limit'] = $limit; + + $options['order_by'] = "annotation_calculation $orderdir"; + + return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Adds an entry in $CONFIG[$register_name][$subregister_name]. + * + * @deprecated 1.8 Use the new menu system. + * + * This is only used for the site-wide menu. See {@link add_menu()}. + * + * @param string $register_name The name of the top-level register + * @param string $subregister_name The name of the subregister + * @param mixed $subregister_value The value of the subregister + * @param array $children_array Optionally, an array of children + * + * @return true|false Depending on success + */ +function add_to_register($register_name, $subregister_name, $subregister_value, $children_array = array()) { + elgg_deprecated_notice("add_to_register() has been deprecated", 1.8); + global $CONFIG; + + if (empty($register_name) || empty($subregister_name)) { + return false; + } + + if (!isset($CONFIG->registers)) { + $CONFIG->registers = array(); + } + + if (!isset($CONFIG->registers[$register_name])) { + $CONFIG->registers[$register_name] = array(); + } + + $subregister = new stdClass; + $subregister->name = $subregister_name; + $subregister->value = $subregister_value; + + if (is_array($children_array)) { + $subregister->children = $children_array; + } + + $CONFIG->registers[$register_name][$subregister_name] = $subregister; + return true; +} + +/** + * Removes a register entry from $CONFIG[register_name][subregister_name] + * + * @deprecated 1.8 Use the new menu system. + * + * This is used to by {@link remove_menu()} to remove site-wide menu items. + * + * @param string $register_name The name of the top-level register + * @param string $subregister_name The name of the subregister + * + * @return true|false Depending on success + * @since 1.7.0 + */ +function remove_from_register($register_name, $subregister_name) { + elgg_deprecated_notice("remove_from_register() has been deprecated", 1.8); + global $CONFIG; + + if (empty($register_name) || empty($subregister_name)) { + return false; + } + + if (!isset($CONFIG->registers)) { + return false; + } + + if (!isset($CONFIG->registers[$register_name])) { + return false; + } + + if (isset($CONFIG->registers[$register_name][$subregister_name])) { + unset($CONFIG->registers[$register_name][$subregister_name]); + return true; + } + + return false; +} + +/** + * If it exists, returns a particular register as an array + * + * @deprecated 1.8 Use the new menu system + * + * @param string $register_name The name of the register + * + * @return array|false Depending on success + */ +function get_register($register_name) { + elgg_deprecated_notice("get_register() has been deprecated", 1.8); + global $CONFIG; + + if ($register_name == 'menu') { + // backward compatible code for site menu + $menu = $CONFIG->menus['site']; + $builder = new ElggMenuBuilder($menu); + $menu_items = $builder->getMenu('text'); + $menu_items = $menu_items['default']; + + $menu = array(); + foreach ($menu_items as $item) { + $subregister = new stdClass; + $subregister->name = $item->getText(); + $subregister->value = $item->getHref(); + $menu[$subregister->name] = $subregister; + } + return $menu; + } + + if (isset($CONFIG->registers[$register_name])) { + return $CONFIG->registers[$register_name]; + } + + return false; +} + +/** + * Deprecated events core function. Code divided between elgg_register_event_handler() + * and trigger_elgg_event(). + * + * @deprecated 1.8 Use explicit register/trigger event functions + * + * @param string $event The type of event (eg 'init', 'update', 'delete') + * @param string $object_type The type of object (eg 'system', 'blog', 'user') + * @param string $function The name of the function that will handle the event + * @param int $priority Priority to call handler. Lower numbers called first (default 500) + * @param boolean $call Set to true to call the event rather than add to it (default false) + * @param mixed $object Optionally, the object the event is being performed on (eg a user) + * + * @return true|false Depending on success + */ +function events($event = "", $object_type = "", $function = "", $priority = 500, $call = false, $object = null) { + + elgg_deprecated_notice('events() has been deprecated.', 1.8); + + // leaving this here just in case someone was directly calling this internal function + if (!$call) { + return elgg_register_event_handler($event, $object_type, $function, $priority); + } else { + return trigger_elgg_event($event, $object_type, $object); + } +} + +/** + * Alias function for events, that registers a function to a particular kind of event + * + * @deprecated 1.8 Use elgg_register_event_handler() instead + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @return true|false Depending on success + */ +function register_elgg_event_handler($event, $object_type, $callback, $priority = 500) { + elgg_deprecated_notice("register_elgg_event_handler() was deprecated by elgg_register_event_handler()", 1.8); + return elgg_register_event_handler($event, $object_type, $callback, $priority); +} + +/** + * Unregisters a function to a particular kind of event + * + * @deprecated 1.8 Use elgg_unregister_event_handler instead + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @since 1.7.0 + */ +function unregister_elgg_event_handler($event, $object_type, $callback) { + elgg_deprecated_notice('unregister_elgg_event_handler => elgg_unregister_event_handler', 1.8); + elgg_unregister_event_handler($event, $object_type, $callback); +} + +/** + * Alias function for events, that triggers a particular kind of event + * + * @deprecated 1.8 Use elgg_trigger_event() instead + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @return true|false Depending on success + */ +function trigger_elgg_event($event, $object_type, $object = null) { + elgg_deprecated_notice('trigger_elgg_event() was deprecated by elgg_trigger_event()', 1.8); + return elgg_trigger_event($event, $object_type, $object); +} + +/** + * Register a function to a plugin hook for a particular entity type, with a given priority. + * + * @deprecated 1.8 Use elgg_register_plugin_hook_handler() instead + * + * eg if you want the function "export_user" to be called when the hook "export" for "user" entities + * is run, use: + * + * register_plugin_hook("export", "user", "export_user"); + * + * "all" is a valid value for both $hook and $entity_type. "none" is a valid value for $entity_type. + * + * The export_user function would then be defined as: + * + * function export_user($hook, $entity_type, $returnvalue, $params); + * + * Where $returnvalue is the return value returned by the last function returned by the hook, and + * $params is an array containing a set of parameters (or nothing). + * + * @param string $hook The name of the hook + * @param string $entity_type The name of the type of entity (eg "user", "object" etc) + * @param string $function The name of a valid function to be run + * @param string $priority The priority - 0 is first, 1000 last, default is 500 + * @return true|false Depending on success + */ +function register_plugin_hook($hook, $type, $callback, $priority = 500) { + elgg_deprecated_notice("register_plugin_hook() was deprecated by elgg_register_plugin_hook_handler()", 1.8); + return elgg_register_plugin_hook_handler($hook, $type, $callback, $priority); +} + +/** + * Unregister a function to a plugin hook for a particular entity type + * + * @deprecated 1.8 Use elgg_unregister_plugin_hook_handler() instead + * + * @param string $hook The name of the hook + * @param string $entity_type The name of the type of entity (eg "user", "object" etc) + * @param string $function The name of a valid function to be run + * @since 1.7.0 + */ +function unregister_plugin_hook($hook, $entity_type, $callback) { + elgg_deprecated_notice("unregister_plugin_hook() was deprecated by elgg_unregister_plugin_hook_handler()", 1.8); + elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback); +} + +/** + * Triggers a plugin hook, with various parameters as an array. For example, to provide + * a 'foo' hook that concerns an entity of type 'bar', with a parameter called 'param1' + * with value 'value1', that by default returns true, you'd call: + * + * @deprecated 1.8 Use elgg_trigger_plugin_hook() instead + * + * trigger_plugin_hook('foo', 'bar', array('param1' => 'value1'), true); + * + * @see register_plugin_hook + * @param string $hook The name of the hook to trigger + * @param string $entity_type The name of the entity type to trigger it for (or "all", or "none") + * @param array $params Any parameters. It's good practice to name the keys, i.e. by using array('name' => 'value', 'name2' => 'value2') + * @param mixed $returnvalue An initial return value + * @return mixed|null The cumulative return value for the plugin hook functions + */ +function trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) { + elgg_deprecated_notice("trigger_plugin_hook() was deprecated by elgg_trigger_plugin_hook()", 1.8); + return elgg_trigger_plugin_hook($hook, $type, $params, $returnvalue); +} + +/** + * Checks if code is being called from a certain function. + * + * To use, call this function with the function name (and optional + * file location) that it has to be called from, it will either + * return true or false. + * + * e.g. + * + * function my_secure_function() + * { + * if (!call_gatekeeper("my_call_function")) + * return false; + * + * ... do secure stuff ... + * } + * + * function my_call_function() + * { + * // will work + * my_secure_function(); + * } + * + * function bad_function() + * { + * // Will not work + * my_secure_function(); + * } + * + * @param mixed $function The function that this function must have in its call stack, + * to test against a method pass an array containing a class and + * method name. + * @param string $file Optional file that the function must reside in. + * + * @return bool + * + * @deprecated 1.8 A neat but pointless function + */ +function call_gatekeeper($function, $file = "") { + elgg_deprecated_notice("call_gatekeeper() is neat but pointless", 1.8); + // Sanity check + if (!$function) { + return false; + } + + // Check against call stack to see if this is being called from the correct location + $callstack = debug_backtrace(); + $stack_element = false; + + foreach ($callstack as $call) { + if (is_array($function)) { + if ((strcmp($call['class'], $function[0]) == 0) && (strcmp($call['function'], $function[1]) == 0)) { + $stack_element = $call; + } + } else { + if (strcmp($call['function'], $function) == 0) { + $stack_element = $call; + } + } + } + + if (!$stack_element) { + return false; + } + + // If file then check that this it is being called from this function + if ($file) { + $mirror = null; + + if (is_array($function)) { + $mirror = new ReflectionMethod($function[0], $function[1]); + } else { + $mirror = new ReflectionFunction($function); + } + + if ((!$mirror) || (strcmp($file, $mirror->getFileName()) != 0)) { + return false; + } + } + + return true; +} + +/** + * This function checks to see if it is being called at somepoint by a function defined somewhere + * on a given path (optionally including subdirectories). + * + * This function is similar to call_gatekeeper() but returns true if it is being called + * by a method or function which has been defined on a given path or by a specified file. + * + * @param string $path The full path and filename that this function must have + * in its call stack If a partial path is given and + * $include_subdirs is true, then the function will return + * true if called by any function in or below the specified path. + * @param bool $include_subdirs Are subdirectories of the path ok, or must you specify an + * absolute path and filename. + * @param bool $strict_mode If true then the calling method or function must be directly + * called by something on $path, if false the whole call stack is + * searched. + * + * @return void + * + * @deprecated 1.8 A neat but pointless function + */ +function callpath_gatekeeper($path, $include_subdirs = true, $strict_mode = false) { + elgg_deprecated_notice("callpath_gatekeeper() is neat but pointless", 1.8); + + global $CONFIG; + + $path = sanitise_string($path); + + if ($path) { + $callstack = debug_backtrace(); + + foreach ($callstack as $call) { + $call['file'] = str_replace("\\", "/", $call['file']); + + if ($include_subdirs) { + if (strpos($call['file'], $path) === 0) { + + if ($strict_mode) { + $callstack[1]['file'] = str_replace("\\", "/", $callstack[1]['file']); + if ($callstack[1] === $call) { + return true; + } + } else { + return true; + } + } + } else { + if (strcmp($path, $call['file']) == 0) { + if ($strict_mode) { + if ($callstack[1] === $call) { + return true; + } + } else { + return true; + } + } + } + + } + return false; + } + + if (isset($CONFIG->debug)) { + system_message("Gatekeeper'd function called from {$callstack[1]['file']}:" . "{$callstack[1]['line']}\n\nStack trace:\n\n" . print_r($callstack, true)); + } + + return false; +} + +/** + * Returns SQL where clause for owner and containers. + * + * @deprecated 1.8 Use elgg_get_guid_based_where_sql(); + * + * @param string $table Entity table prefix as defined in SELECT...FROM entities $table + * @param NULL|array $owner_guids Owner GUIDs + * + * @return FALSE|str + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_owner_where_sql($table, $owner_guids) { + elgg_deprecated_notice('elgg_get_entity_owner_where_sql() is deprecated by elgg_get_guid_based_where_sql().', 1.8); + + return elgg_get_guid_based_where_sql("{$table}.owner_guid", $owner_guids); +} + +/** + * Returns SQL where clause for containers. + * + * @deprecated 1.8 Use elgg_get_guid_based_where_sql(); + * + * @param string $table Entity table prefix as defined in + * SELECT...FROM entities $table + * @param NULL|array $container_guids Array of container guids + * + * @return FALSE|string + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_container_where_sql($table, $container_guids) { + elgg_deprecated_notice('elgg_get_entity_container_where_sql() is deprecated by elgg_get_guid_based_where_sql().', 1.8); + + return elgg_get_guid_based_where_sql("{$table}.container_guid", $container_guids); +} + +/** + * Returns SQL where clause for site entities + * + * @deprecated 1.8 Use elgg_get_guid_based_where_sql() + * + * @param string $table Entity table prefix as defined in SELECT...FROM entities $table + * @param NULL|array $site_guids Array of site guids + * + * @return FALSE|string + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_site_where_sql($table, $site_guids) { + elgg_deprecated_notice('elgg_get_entity_site_where_sql() is deprecated by elgg_get_guid_based_where_sql().', 1.8); + + return elgg_get_guid_based_where_sql("{$table}.site_guid", $site_guids); +} + +/** + * Return an array of objects in a given container. + * + * @see get_entities() + * + * @param int $group_guid The container (defaults to current page owner) + * @param string $subtype The subtype + * @param int $owner_guid Owner + * @param int $site_guid The site + * @param string $order_by Order + * @param int $limit Limit on number of elements to return, by default 10. + * @param int $offset Where to start, by default 0. + * @param bool $count Whether to return the entities or a count of them. + * + * @return array|false + * @deprecated 1.8 Use elgg_get_entities() instead + */ +function get_objects_in_group($group_guid, $subtype = "", $owner_guid = 0, $site_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = FALSE) { + elgg_deprecated_notice("get_objects_in_group was deprected in 1.8. Use elgg_get_entities() instead", 1.8); + + 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; + } + + $container_guid = (int)$group_guid; + if ($container_guid == 0) { + $container_guid = elgg_get_page_owner_guid(); + } + + $where = array(); + + $where[] = "e.type='object'"; + + if (!empty($subtype)) { + if (!$subtype = get_subtype_id('object', $subtype)) { + return FALSE; + } + $where[] = "e.subtype=$subtype"; + } + if ($owner_guid != "") { + if (!is_array($owner_guid)) { + $owner_guid = (int)$owner_guid; + $where[] = "e.container_guid = '$owner_guid'"; + } else if (sizeof($owner_guid) > 0) { + // Cast every element to the owner_guid array to int + $owner_guid = array_map("sanitise_int", $owner_guid); + $owner_guid = implode(",", $owner_guid); + $where[] = "e.container_guid in ({$owner_guid})"; + } + } + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + + if ($container_guid > 0) { + $where[] = "e.container_guid = {$container_guid}"; + } + + if (!$count) { + $query = "SELECT * from {$CONFIG->dbprefix}entities e" . " join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid where "; + } else { + $query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e" . " join {$CONFIG->dbprefix}objects_entity o on e.guid=o.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"; + + // 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; + } +} + +/** + * Lists entities that belong to a group. + * + * @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 bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view (default: true) + * @param bool $pagination Whether to display pagination (default: true) + * + * @return string List of parsed entities + * + * @see elgg_list_entities() + * @deprecated 1.8 Use elgg_list_entities() instead + */ +function list_entities_groups($subtype = "", $owner_guid = 0, $container_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true) { + elgg_deprecated_notice("list_entities_groups was deprecated in 1.8. Use elgg_list_entities() instead.", 1.8); + $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, $listtypetoggle, $pagination); +} + +/** + * Get all the entities from metadata from a group. + * + * @param int $group_guid The ID of the group. + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner guid + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any + * @param bool $count Return count instead of entities + * + * @return array|false + * @deprecated 1.8 Use elgg_get_entities_from_metadata() + */ +function get_entities_from_metadata_groups($group_guid, $meta_name, $meta_value = "", $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { + elgg_deprecated_notice("get_entities_from_metadata_groups was deprecated in 1.8.", 1.8); + global $CONFIG; + + $meta_n = get_metastring_id($meta_name); + $meta_v = get_metastring_id($meta_value); + + $entity_type = sanitise_string($entity_type); + $entity_subtype = get_subtype_id($entity_type, $entity_subtype); + $limit = (int)$limit; + $offset = (int)$offset; + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + $order_by = sanitise_string($order_by); + $site_guid = (int)$site_guid; + if (is_array($owner_guid)) { + foreach ($owner_guid as $key => $guid) { + $owner_guid[$key] = (int)$guid; + } + } else { + $owner_guid = (int)$owner_guid; + } + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + $container_guid = (int)$group_guid; + if ($container_guid == 0) { + $container_guid = elgg_get_page_owner_guid(); + } + + $where = array(); + + if ($entity_type != "") { + $where[] = "e.type='$entity_type'"; + } + if ($entity_subtype) { + $where[] = "e.subtype=$entity_subtype"; + } + if ($meta_name != "") { + $where[] = "m.name_id='$meta_n'"; + } + if ($meta_value != "") { + $where[] = "m.value_id='$meta_v'"; + } + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + if ($container_guid > 0) { + $where[] = "e.container_guid = {$container_guid}"; + } + + if (is_array($owner_guid)) { + $where[] = "e.container_guid in (" . implode(",", $owner_guid) . ")"; + } else if ($owner_guid > 0) { + $where[] = "e.container_guid = {$owner_guid}"; + } + + if (!$count) { + $query = "SELECT distinct e.* "; + } else { + $query = "SELECT count(e.guid) as total "; + } + + $query .= "from {$CONFIG->dbprefix}entities e" . " JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid " . " JOIN {$CONFIG->dbprefix}objects_entity o on e.guid = o.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 limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($row = get_data_row($query)) { + return $row->total; + } + } + return false; +} + +/** + * As get_entities_from_metadata_groups() but with multiple entities. + * + * @param int $group_guid The ID of the group. + * @param array $meta_array Array of 'name' => 'value' pairs + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any + * @param bool $count Return count of entities instead of entities + * + * @return int|array List of ElggEntities, or the total number if count is set to false + * @deprecated 1.8 Use elgg_get_entities_from_metadata() + */ +function get_entities_from_metadata_groups_multi($group_guid, $meta_array, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { + elgg_deprecated_notice("get_entities_from_metadata_groups_multi was deprecated in 1.8.", 1.8); + + global $CONFIG; + + if (!is_array($meta_array) || sizeof($meta_array) == 0) { + return false; + } + + $where = array(); + + $mindex = 1; + $join = ""; + foreach ($meta_array as $meta_name => $meta_value) { + $meta_n = get_metastring_id($meta_name); + $meta_v = get_metastring_id($meta_value); + $join .= " JOIN {$CONFIG->dbprefix}metadata m{$mindex} on e.guid = m{$mindex}.entity_guid" . " JOIN {$CONFIG->dbprefix}objects_entity o on e.guid = o.guid "; + + if ($meta_name != "") { + $where[] = "m{$mindex}.name_id='$meta_n'"; + } + + if ($meta_value != "") { + $where[] = "m{$mindex}.value_id='$meta_v'"; + } + + $mindex++; + } + + $entity_type = sanitise_string($entity_type); + $entity_subtype = get_subtype_id($entity_type, $entity_subtype); + $limit = (int)$limit; + $offset = (int)$offset; + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + $order_by = sanitise_string($order_by); + $owner_guid = (int)$owner_guid; + + $site_guid = (int)$site_guid; + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + //$access = get_access_list(); + + if ($entity_type != "") { + $where[] = "e.type = '{$entity_type}'"; + } + + if ($entity_subtype) { + $where[] = "e.subtype = {$entity_subtype}"; + } + + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + + if ($owner_guid > 0) { + $where[] = "e.owner_guid = {$owner_guid}"; + } + + if ($container_guid > 0) { + $where[] = "e.container_guid = {$container_guid}"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT distinct e.* "; + } + + $query .= " from {$CONFIG->dbprefix}entities e {$join} where"; + foreach ($where as $w) { + $query .= " $w and "; + } + $query .= get_access_sql_suffix("e"); // Add access controls + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * List items within a given geographic area. + * + * @param real $lat Latitude + * @param real $long Longitude + * @param real $radius The radius + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to display per page (default: 10) + * @param bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view + * @param bool $navigation Display pagination? Default: true + * + * @return string A viewable list of entities + * @deprecated 1.8 Use elgg_get_entities_from_location() + */ +function list_entities_in_area($lat, $long, $radius, $type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { + elgg_deprecated_notice('list_entities_in_area() was deprecated. Use elgg_list_entities_from_location()', 1.8); + + $options = array(); + + $options['latitude'] = $lat; + $options['longitude'] = $long; + $options['distance'] = $radius; + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + $options['limit'] = $limit; + + $options['full_view'] = $fullview; + $options['list_type_toggle'] = $listtypetoggle; + $options['pagination'] = $pagination; + + return elgg_list_entities_from_location($options); +} + +/** + * List entities in a given location + * + * @param string $location Location + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to display per page (default: 10) + * @param bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view + * @param bool $navigation Display pagination? Default: true + * + * @return string A viewable list of entities + * @deprecated 1.8 Use elgg_list_entities_from_location() + */ +function list_entities_location($location, $type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { + elgg_deprecated_notice('list_entities_location() was deprecated. Use elgg_list_entities_from_metadata()', 1.8); + + return list_entities_from_metadata('location', $location, $type, $subtype, $owner_guid, $limit, $fullview, $listtypetoggle, $navigation); +} + +/** + * Return entities within a given geographic area. + * + * @param float $lat Latitude + * @param float $long Longitude + * @param float $radius The radius + * @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 Count entities + * @param int $site_guid Site GUID. 0 for current, -1 for any + * @param int|array $container_guid Container GUID + * + * @return array A list of entities. + * @deprecated 1.8 Use elgg_get_entities_from_location() + */ +function get_entities_in_area($lat, $long, $radius, $type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = NULL) { + elgg_deprecated_notice('get_entities_in_area() was deprecated by elgg_get_entities_from_location()!', 1.8); + + $options = array(); + + $options['latitude'] = $lat; + $options['longitude'] = $long; + $options['distance'] = $radius; + + // set container_guid to owner_guid to emulate old functionality + if ($owner_guid != "") { + if (is_null($container_guid)) { + $container_guid = $owner_guid; + } + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($container_guid) { + if (is_array($container_guid)) { + $options['container_guids'] = $container_guid; + } else { + $options['container_guid'] = $container_guid; + } + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_location($options); +} + +/** + * Return a list of entities suitable for display based on the given search criteria. + * + * @see elgg_view_entity_list + * + * @deprecated 1.8 Use elgg_list_entities_from_metadata + * + * @param mixed $meta_name Metadata name to search on + * @param mixed $meta_value The value to match, optionally + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity + * @param int $owner_guid Owner GUID + * @param int $limit Number of entities to display per page + * @param bool $fullview WDisplay the full view (default: true) + * @param bool $listtypetoggle Allow users to toggle to the gallery view. Default: true + * @param bool $pagination Display pagination? Default: true + * @param bool $case_sensitive Case sensitive metadata names? + * + * @return string + * + * @return string A list of entities suitable for display + */ +function list_entities_from_metadata($meta_name, $meta_value = "", $entity_type = ELGG_ENTITIES_ANY_VALUE, $entity_subtype = ELGG_ENTITIES_ANY_VALUE, $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true, $case_sensitive = true) { + + elgg_deprecated_notice('list_entities_from_metadata() was deprecated by elgg_list_entities_from_metadata()!', 1.8); + + $offset = (int)get_input('offset'); + $limit = (int)$limit; + $options = array( + 'metadata_name' => $meta_name, + 'metadata_value' => $meta_value, + 'type' => $entity_type, + 'subtype' => $entity_subtype, + 'limit' => $limit, + 'offset' => $offset, + 'count' => TRUE, + 'metadata_case_sensitive' => $case_sensitive + ); + + // previous function allowed falsy $owner_guid for anything + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + $count = elgg_get_entities_from_metadata($options); + + $options['count'] = FALSE; + $entities = elgg_get_entities_from_metadata($options); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, $pagination); +} + +/** + * Returns a viewable list of entities based on the given search criteria. + * + * @see elgg_view_entity_list + * + * @param array $meta_array Array of 'name' => 'value' pairs + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param bool $fullview WDisplay the full view (default: true) + * @param bool $listtypetoggle Allow users to toggle to the gallery view. Default: true + * @param bool $pagination Display pagination? Default: true + * + * @return string List of ElggEntities suitable for display + * + * @deprecated 1.8 Use elgg_list_entities_from_metadata() instead + */ +function list_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true) { + elgg_deprecated_notice(elgg_echo('deprecated:function', array( + 'list_entities_from_metadata_multi', 'elgg_get_entities_from_metadata')), 1.8); + + $offset = (int)get_input('offset'); + $limit = (int)$limit; + $count = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", $site_guid, true); + $entities = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", $site_guid, false); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, $pagination); +} + +/** + * Deprecated by elgg_register_menu_item(). Set $menu_name to 'page'. + * + * @see elgg_register_menu_item() + * @deprecated 1.8 Use the new menu system + * + * @param string $label The label + * @param string $link The link + * @param string $group The group to store item in + * @param boolean $onclick Add a confirmation when clicked? + * @param boolean $selected Is menu item selected + * + * @return bool + */ +function add_submenu_item($label, $link, $group = 'default', $onclick = false, $selected = NULL) { + elgg_deprecated_notice('add_submenu_item was deprecated by elgg_register_menu_item', 1.8); + + // submenu items were added in the page setup hook usually by checking + // the context. We'll pass in the current context here, which will + // emulate that effect. + // if context == 'main' (default) it probably means they always wanted + // the menu item to show up everywhere. + $context = elgg_get_context(); + + if ($context == 'main') { + $context = 'all'; + } + + $item = array('name' => $label, 'text' => $label, 'href' => $link, 'context' => $context, + 'section' => $group,); + + if ($selected) { + $item['selected'] = true; + } + + if ($onclick) { + $js = "onclick=\"javascript:return confirm('" . elgg_echo('deleteconfirm') . "')\""; + $item['vars'] = array('js' => $js); + } + + return elgg_register_menu_item('page', $item); +} + +/** + * Remove an item from submenu by label + * + * @deprecated 1.8 Use the new menu system + * @see elgg_unregister_menu_item() + * + * @param string $label The item label + * @param string $group The submenu group (default "a") + * @return bool whether the item was removed or not + * @since 1.7.8 + */ +function remove_submenu_item($label, $group = 'a') { + elgg_deprecated_notice('remove_submenu_item was deprecated by elgg_unregister_menu_item', 1.8); + + return elgg_unregister_menu_item('page', $label); +} + +/** + * Use elgg_view_menu(). Set $menu_name to 'owner_block'. + * + * @see elgg_view_menu() + * @deprecated 1.8 Use the new menu system. elgg_view_menu() + * + * @return string + */ +function get_submenu() { + elgg_deprecated_notice("get_submenu() has been deprecated by elgg_view_menu()", 1.8); + return elgg_view_menu('owner_block', array('entity' => $owner, + 'class' => 'elgg-menu-owner-block',)); +} + +/** + * Adds an item to the site-wide menu. + * + * You can obtain the menu array by calling {@link get_register('menu')} + * + * @param string $menu_name The name of the menu item + * @param string $menu_url The URL of the page + * @param array $menu_children Optionally, an array of submenu items (not used) + * @param string $context (not used) + * + * @return true|false Depending on success + * @deprecated 1.8 use elgg_register_menu_item() for the menu 'site' + */ +function add_menu($menu_name, $menu_url, $menu_children = array(), $context = "") { + elgg_deprecated_notice('add_menu() deprecated by elgg_register_menu_item()', 1.8); + + return elgg_register_menu_item('site', array('name' => $menu_name, 'text' => $menu_name, + 'href' => $menu_url,)); +} + +/** + * Removes an item from the menu register + * + * @param string $menu_name The name of the menu item + * + * @return true|false Depending on success + * @deprecated 1.8 Use the new menu system + */ +function remove_menu($menu_name) { + elgg_deprecated_notice("remove_menu() deprecated by elgg_unregister_menu_item()", 1.8); + return elgg_unregister_menu_item('site', $menu_name); +} + +/** + * When given a title, returns a version suitable for inclusion in a URL + * + * @param string $title The title + * + * @return string The optimised title + * @deprecated 1.8 Use elgg_get_friendly_title() + */ +function friendly_title($title) { + elgg_deprecated_notice('friendly_title was deprecated by elgg_get_friendly_title', 1.8); + return elgg_get_friendly_title($title); +} + +/** + * Displays a UNIX timestamp in a friendly way (eg "less than a minute ago") + * + * @param int $time A UNIX epoch timestamp + * + * @return string The friendly time + * @deprecated 1.8 Use elgg_view_friendly_time() + */ +function friendly_time($time) { + elgg_deprecated_notice('friendly_time was deprecated by elgg_view_friendly_time', 1.8); + return elgg_view_friendly_time($time); +} + +/** + * Filters a string into an array of significant words + * + * @deprecated 1.8 Don't use this. + * + * @param string $string A string + * + * @return array + */ +function filter_string($string) { + elgg_deprecated_notice('filter_string() was deprecated!', 1.8); + + // Convert it to lower and trim + $string = strtolower($string); + $string = trim($string); + + // Remove links and email addresses + // match protocol://address/path/file.extension?some=variable&another=asf% + $string = preg_replace("/\s([a-zA-Z]+:\/\/[a-z][a-z0-9\_\.\-]*[a-z]{2,6}" . "[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu", " ", $string); + + // match www.something.domain/path/file.extension?some=variable&another=asf% + $string = preg_replace("/\s(www\.[a-z][a-z0-9\_\.\-]*[a-z]{2,6}" . "[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu", " ", $string); + + // match name@address + $string = preg_replace("/\s([a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]" . "*\@[a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]{2,6})([\s|\.|\,])/iu", " ", $string); + + // Sanitise the string; remove unwanted characters + $string = preg_replace('/\W/ui', ' ', $string); + + // Explode it into an array + $terms = explode(' ', $string); + + // Remove any blacklist terms + //$terms = array_filter($terms, 'remove_blacklist'); + + return $terms; +} + +/** + * Returns true if the word in $input is considered significant + * + * @deprecated 1.8 Don't use this. + * + * @param string $input A word + * + * @return true|false + */ +function remove_blacklist($input) { + elgg_deprecated_notice('remove_blacklist() was deprecated!', 1.8); + + global $CONFIG; + + if (!is_array($CONFIG->wordblacklist)) { + return $input; + } + + if (strlen($input) < 3 || in_array($input, $CONFIG->wordblacklist)) { + return false; + } + + return true; +} + +/** + * Gets the guid of the entity that owns the current page. + * + * @deprecated 1.8 Use elgg_get_page_owner_guid() + * + * @return int The current page owner guid (0 if none). + */ +function page_owner() { + elgg_deprecated_notice('page_owner() was deprecated by elgg_get_page_owner_guid().', 1.8); + return elgg_get_page_owner_guid(); +} + +/** + * Gets the owner entity for the current page. + * + * @deprecated 1.8 Use elgg_get_page_owner_entity() + * @return ElggEntity|false The current page owner or false if none. + */ +function page_owner_entity() { + elgg_deprecated_notice('page_owner_entity() was deprecated by elgg_get_page_owner_entity().', 1.8); + return elgg_get_page_owner_entity(); +} + +/** + * Registers a page owner handler function + * + * @param string $functionname The callback function + * + * @deprecated 1.8 Use the 'page_owner', 'system' plugin hook + * @return void + */ +function add_page_owner_handler($functionname) { + elgg_deprecated_notice("add_page_owner_handler() was deprecated by the plugin hook 'page_owner', 'system'.", 1.8); +} + +/** + * Set a page owner entity + * + * @param int $entitytoset The GUID of the entity + * + * @deprecated 1.8 Use elgg_set_page_owner_guid() + * @return void + */ +function set_page_owner($entitytoset = -1) { + elgg_deprecated_notice('set_page_owner() was deprecated by elgg_set_page_owner_guid().', 1.8); + elgg_set_page_owner_guid($entitytoset); +} + +/** + * Sets the functional context of a page + * + * @deprecated 1.8 Use elgg_set_context() + * + * @param string $context The context of the page + * + * @return mixed Either the context string, or false on failure + */ +function set_context($context) { + elgg_deprecated_notice('set_context() was deprecated by elgg_set_context().', 1.8); + elgg_set_context($context); + if (empty($context)) { + return false; + } + return $context; +} + +/** + * Returns the functional context of a page + * + * @deprecated 1.8 Use elgg_get_context() + * + * @return string The context, or 'main' if no context has been provided + */ +function get_context() { + elgg_deprecated_notice('get_context() was deprecated by elgg_get_context().', 1.8); + return elgg_get_context(); + + // @todo - used to set context based on calling script + // $context = get_plugin_name(true) +} + +/** + * Returns a list of plugins to load, in the order that they should be loaded. + * + * @deprecated 1.8 Use elgg_get_plugin_ids_in_dir() or elgg_get_plugins() + * + * @return array List of plugins + */ +function get_plugin_list() { + elgg_deprecated_notice('get_plugin_list() is deprecated by elgg_get_plugin_ids_in_dir() or elgg_get_plugins()', 1.8); + + $plugins = elgg_get_plugins('any'); + + $list = array(); + if ($plugins) { + foreach ($plugins as $i => $plugin) { + // in <=1.7 this returned indexed by multiples of 10. + // uh...sure...why not. + $index = ($i + 1) * 10; + $list[$index] = $plugin->getID(); + } + } + + return $list; +} + +/** + * Regenerates the list of known plugins and saves it to the current site + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * elgg_regenerate_simplecache(); + * elgg_reset_system_cache(); + * + * @deprecated 1.8 Use elgg_generate_plugin_entities() and elgg_set_plugin_priorities() + * + * @param array $pluginorder Optionally, a list of existing plugins and their orders + * + * @return array The new list of plugins and their orders + */ +function regenerate_plugin_list($pluginorder = FALSE) { + $msg = 'regenerate_plugin_list() is (sorta) deprecated by elgg_generate_plugin_entities() and' + . ' elgg_set_plugin_priorities().'; + elgg_deprecated_notice($msg, 1.8); + + // they're probably trying to set it? + if ($pluginorder) { + if (elgg_generate_plugin_entities()) { + // sort the plugins by the index numerically since we used + // weird indexes in the old system. + ksort($pluginorder, SORT_NUMERIC); + return elgg_set_plugin_priorities($pluginorder); + } + return false; + } else { + // they're probably trying to regenerate from disk? + return elgg_generate_plugin_entities(); + } +} + +/** + * Get the name of the most recent plugin to be called in the + * call stack (or the plugin that owns the current page, if any). + * + * i.e., if the last plugin was in /mod/foobar/, get_plugin_name would return foo_bar. + * + * @deprecated 1.8 Use elgg_get_calling_plugin_id() + * + * @param boolean $mainfilename If set to true, this will instead determine the + * context from the main script filename called by + * the browser. Default = false. + * + * @return string|false Plugin name, or false if no plugin name was called + */ +function get_plugin_name($mainfilename = false) { + elgg_deprecated_notice('get_plugin_name() is deprecated by elgg_get_calling_plugin_id()', 1.8); + + return elgg_get_calling_plugin_id($mainfilename); +} + +/** + * Load and parse a plugin manifest from a plugin XML file. + * + * @example plugins/manifest.xml Example 1.8-style manifest file. + * + * @deprecated 1.8 Use ElggPlugin->getManifest() + * + * @param string $plugin Plugin name. + * @return array of values + */ +function load_plugin_manifest($plugin) { + elgg_deprecated_notice('load_plugin_manifest() is deprecated by ElggPlugin->getManifest()', 1.8); + + $xml_file = elgg_get_plugins_path() . "$plugin/manifest.xml"; + + try { + $manifest = new ElggPluginManifest($xml_file, $plugin); + } catch(Exception $e) { + return false; + } + + return $manifest->getManifest(); +} + +/** + * This function checks a plugin manifest 'elgg_version' value against the current install + * returning TRUE if the elgg_version is >= the current install's version. + * + * @deprecated 1.8 Use ElggPlugin->canActivate() + * + * @param string $manifest_elgg_version_string The build version (eg 2009010201). + * @return bool + */ +function check_plugin_compatibility($manifest_elgg_version_string) { + elgg_deprecated_notice('check_plugin_compatibility() is deprecated by ElggPlugin->canActivate()', 1.8); + + $version = get_version(); + + if (strpos($manifest_elgg_version_string, '.') === false) { + // Using version + $req_version = (int)$manifest_elgg_version_string; + + return ($version >= $req_version); + } + + return false; +} + +/** + * Shorthand function for finding the plugin settings. + * + * @deprecated 1.8 Use elgg_get_calling_plugin_entity() or elgg_get_plugin_from_id() + * + * @param string $plugin_id Optional plugin id, if not specified + * then it is detected from where you are calling. + * + * @return mixed + */ +function find_plugin_settings($plugin_id = null) { + elgg_deprecated_notice('find_plugin_setting() is deprecated by elgg_get_calling_plugin_entity() or elgg_get_plugin_from_id()', 1.8); + if ($plugin_id) { + return elgg_get_plugin_from_id($plugin_id); + } else { + return elgg_get_calling_plugin_entity(); + } +} + +/** + * Return an array of installed plugins. + * + * @deprecated 1.8 use elgg_get_plugins() + * + * @param string $status any|enabled|disabled + * @return array + */ +function get_installed_plugins($status = 'all') { + global $CONFIG; + + elgg_deprecated_notice('get_installed_plugins() was deprecated by elgg_get_plugins()', 1.8); + + $plugins = elgg_get_plugins($status); + + if (!$plugins) { + return array(); + } + + $installed_plugins = array(); + + foreach ($plugins as $plugin) { + if (!$plugin->isValid()) { + continue; + } + + $include = true; + + if ($status == 'enabled' && !$plugin->isActive()) { + $include = false; + } elseif ($status == 'disabled' && $plugin->isActive()) { + $include = true; + } + + if ($include) { + $installed_plugins[$plugin->getID()] = array( + 'active' => $plugin->isActive(), + 'manifest' => $plugin->getManifest()->getManifest() + ); + } + } + + return $installed_plugins; +} + +/** + * Enable a plugin for a site (default current site) + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * elgg_regenerate_simplecache(); + * elgg_reset_system_cache(); + * + * @deprecated 1.8 Use ElggPlugin->activate() + * + * @param string $plugin The plugin name. + * @param int $site_guid The site id, if not specified then this is detected. + * + * @return array + * @throws InvalidClassException + */ +function enable_plugin($plugin, $site_guid = null) { + elgg_deprecated_notice('enable_plugin() was deprecated by ElggPlugin->activate()', 1.8); + + $plugin = sanitise_string($plugin); + + $site_guid = (int) $site_guid; + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; + } + + try { + $plugin = new ElggPlugin($plugin); + } catch(Exception $e) { + return false; + } + + if (!$plugin->canActivate($site_guid)) { + return false; + } + + return $plugin->activate($site_guid); +} + +/** + * Disable a plugin for a site (default current site) + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * elgg_regenerate_simplecache(); + * elgg_reset_system_cache(); + * + * @deprecated 1.8 Use ElggPlugin->deactivate() + * + * @param string $plugin The plugin name. + * @param int $site_guid The site id, if not specified then this is detected. + * + * @return bool + * @throws InvalidClassException + */ +function disable_plugin($plugin, $site_guid = 0) { + elgg_deprecated_notice('disable_plugin() was deprecated by ElggPlugin->deactivate()', 1.8); + + $plugin = sanitise_string($plugin); + + $site_guid = (int) $site_guid; + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; + } + + try { + $plugin = new ElggPlugin($plugin); + } catch(Exception $e) { + return false; + } + + return $plugin->deactivate($site_guid); +} + +/** + * Return whether a plugin is enabled or not. + * + * @deprecated 1.8 Use elgg_is_active_plugin() + * + * @param string $plugin The plugin name. + * @param int $site_guid The site id, if not specified then this is detected. + * + * @return bool + */ +function is_plugin_enabled($plugin, $site_guid = 0) { + elgg_deprecated_notice('is_plugin_enabled() was deprecated by elgg_is_active_plugin()', 1.8); + return elgg_is_active_plugin($plugin, $site_guid); +} + +/** + * Get entities based on their private data. + * + * @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 Return a count of entities + * @param int $site_guid The site to get entities for. 0 for current, -1 for any + * @param mixed $container_guid The container(s) GUIDs + * + * @return array A list of entities. + * @deprecated 1.8 Use elgg_get_entities_from_private_settings() + */ +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) { + elgg_deprecated_notice('get_entities_from_private_setting() was deprecated by elgg_get_entities_from_private_setting()!', 1.8); + + $options = array(); + + $options['private_setting_name'] = $name; + $options['private_setting_value'] = $value; + + // set container_guid to owner_guid to emulate old functionality + if ($owner_guid != "") { + if (is_null($container_guid)) { + $container_guid = $owner_guid; + } + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($container_guid) { + if (is_array($container_guid)) { + $options['container_guids'] = $container_guid; + } else { + $options['container_guid'] = $container_guid; + } + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_private_settings($options); +} + +/** + * Get entities based on their private data by multiple keys. + * + * @param string $name The name of the setting + * @param mixed $type Entity type + * @param string $subtype Entity subtype + * @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 bool $count Count entities + * @param int $site_guid Site GUID. 0 for current, -1 for any. + * @param mixed $container_guid Container GUID + * + * @return array A list of entities. + * @deprecated 1.8 Use elgg_get_entities_from_private_settings() + */ +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) { + + elgg_deprecated_notice('get_entities_from_private_setting_multi() was deprecated by elgg_get_entities_from_private_settings()!', 1.8); + + $options = array(); + + $pairs = array(); + foreach ($name as $setting_name => $setting_value) { + $pairs[] = array('name' => $setting_name, 'value' => $setting_value); + } + $options['private_setting_name_value_pairs'] = $pairs; + + // set container_guid to owner_guid to emulate old functionality + if ($owner_guid != "") { + if (is_null($container_guid)) { + $container_guid = $owner_guid; + } + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($container_guid) { + if (is_array($container_guid)) { + $options['container_guids'] = $container_guid; + } else { + $options['container_guid'] = $container_guid; + } + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_private_settings($options); +} + +/** + * Returns a viewable list of entities by relationship + * + * @see elgg_view_entity_list + * + * @deprecated 1.8 Use elgg_list_entities_from_relationship() + * + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Reverse the normal function of the query to instead say "give me all entities for whome $relationship_guid is a $relationship of" + * @param string $type The type of entity (eg 'object') + * @param string $subtype The entity subtype + * @param int $owner_guid The owner (default: all) + * @param int $limit The number of entities to display on a page + * @param true|false $fullview Whether or not to display the full view (default: true) + * @param true|false $viewtypetoggle Whether or not to allow gallery view + * @param true|false $pagination Whether to display pagination (default: true) + * @param bool $order_by SQL order by clause + * @return string The viewable list of entities + */ +function list_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = ELGG_ENTITIES_ANY_VALUE, +$subtype = ELGG_ENTITIES_ANY_VALUE, $owner_guid = 0, $limit = 10, +$fullview = true, $listtypetoggle = false, $pagination = true, $order_by = '') { + + elgg_deprecated_notice("list_entities_from_relationship was deprecated by elgg_list_entities_from_relationship()!", 1.8); + return elgg_list_entities_from_relationship(array( + 'relationship' => $relationship, + 'relationship_guid' => $relationship_guid, + 'inverse_relationship' => $inverse_relationship, + 'type' => $type, + 'subtype' => $subtype, + 'owner_guid' => $owner_guid, + 'order_by' => $order_by, + 'limit' => $limit, + 'full_view' => $fullview, + 'list_type_toggle' => $listtypetoggle, + 'pagination' => $pagination, + )); +} + +/** + * Gets the number of entities by a the number of entities related to them in a particular way. + * This is a good way to get out the users with the most friends, or the groups with the + * most members. + * + * @deprecated 1.8 Use elgg_get_entities_from_relationship_count() + * + * @param string $relationship The relationship eg "friends_of" + * @param bool $inverse_relationship Inverse relationship owners + * @param string $type The type of entity (default: all) + * @param string $subtype The entity subtype (default: all) + * @param int $owner_guid The owner of the entities (default: none) + * @param int $limit Limit + * @param int $offset Offset + * @param bool $count Return a count instead of entities + * @param int $site_guid Site GUID + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + */ +function get_entities_by_relationship_count($relationship, $inverse_relationship = true, $type = "", +$subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + elgg_deprecated_notice('get_entities_by_relationship_count() is deprecated by elgg_get_entities_from_relationship_count()', 1.8); + + $options = array(); + + $options['relationship'] = $relationship; + + // this used to default to true, which is wrong. + // flip it for the new function + $options['inverse_relationship'] = !$inverse_relationship; + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_relationship_count($options); +} + +/** + * Displays a human-readable list of entities + * + * @deprecated 1.8 Use elgg_list_entities_from_relationship_count() + * + * @param string $relationship The relationship eg "friends_of" + * @param bool $inverse_relationship Inverse relationship owners + * @param string $type The type of entity (eg 'object') + * @param string $subtype The entity subtype + * @param int $owner_guid The owner (default: all) + * @param int $limit The number of entities to display on a page + * @param bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view + * @param bool $pagination Whether to display pagination (default: true) + * + * @return string The viewable list of entities + */ +function list_entities_by_relationship_count($relationship, $inverse_relationship = true, +$type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, +$listtypetoggle = false, $pagination = true) { + + elgg_deprecated_notice('list_entities_by_relationship_count() was deprecated by elgg_list_entities_from_relationship_count()', 1.8); + + $options = array(); + + $options['relationship'] = $relationship; + + // this used to default to true, which is wrong. + // flip it for the new function + $options['inverse_relationship'] = !$inverse_relationship; + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + $options['limit'] = $limit; + + $options['full_view'] = $fullview; + + return elgg_list_entities_from_relationship_count($options); +} + +/** + * Gets the number of entities by a the number of entities related to + * them in a particular way also constrained by metadata. + * + * @deprecated 1.8 Use elgg_get_entities_from_relationship() + * + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Inverse relationship owner + * @param String $meta_name The metadata name + * @param String $meta_value The metadata value + * @param string $type The type of entity (default: all) + * @param string $subtype The entity subtype (default: all) + * @param int $owner_guid The owner of the entities (default: none) + * @param int $limit Limit + * @param int $offset Offset + * @param bool $count Return a count instead of entities + * @param int $site_guid Site GUID + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + */ +function get_entities_from_relationships_and_meta($relationship, $relationship_guid, +$inverse_relationship = false, $meta_name = "", $meta_value = "", $type = "", +$subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + + elgg_deprecated_notice('get_entities_from_relationship_and_meta() was deprecated by elgg_get_entities_from_relationship()!', 1.7); + + $options = array(); + + $options['relationship'] = $relationship; + $options['relationship_guid'] = $relationship_guid; + $options['inverse_relationship'] = $inverse_relationship; + + if ($meta_value) { + $options['values'] = $meta_value; + } + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_relationship($options); +} + + +/** + * Retrieves items from the river. All parameters are optional. + * + * @param int|array $subject_guid Acting entity to restrict to. Default: all + * @param int|array $object_guid Entity being acted on to restrict to. Default: all + * @param string $subject_relationship If set to a relationship type, this will use + * $subject_guid as the starting point and set the + * subjects to be all users this + * entity has this relationship with (eg 'friend'). + * Default: blank + * @param string $type The type of entity to restrict to. Default: all + * @param string $subtype The subtype of entity to restrict to. Default: all + * @param string $action_type The type of river action to restrict to. Default: all + * @param int $limit The number of items to retrieve. Default: 20 + * @param int $offset The page offset. Default: 0 + * @param int $posted_min The minimum time period to look at. Default: none + * @param int $posted_max The maximum time period to look at. Default: none + * + * @return array|false Depending on success + * @deprecated 1.8 Use elgg_get_river() + */ +function get_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', +$type = '', $subtype = '', $action_type = '', $limit = 20, $offset = 0, $posted_min = 0, +$posted_max = 0) { + elgg_deprecated_notice("get_river_items deprecated by elgg_get_river", 1.8); + + $options = array(); + + if ($subject_guid) { + $options['subject_guid'] = $subject_guid; + } + + if ($object_guid) { + $options['object_guid'] = $object_guid; + } + + if ($subject_relationship) { + $options['relationship'] = $subject_relationship; + unset($options['subject_guid']); + $options['relationship_guid'] = $subject_guid; + } + + if ($type) { + $options['type'] = $type; + } + + if ($subtype) { + $options['subtype'] = $subtype; + } + + if ($action_type) { + $options['action_type'] = $action_type; + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + if ($posted_min) { + $options['posted_time_lower'] = $posted_min; + } + + if ($posted_max) { + $options['posted_time_upper'] = $posted_max; + } + + return elgg_get_river($options); +} + +/** + * Returns a human-readable version of the river. + * + * @param int|array $subject_guid Acting entity to restrict to. Default: all + * @param int|array $object_guid Entity being acted on to restrict to. Default: all + * @param string $subject_relationship If set to a relationship type, this will use + * $subject_guid as the starting point and set + * the subjects to be all users this entity has this + * relationship with (eg 'friend'). Default: blank + * @param string $type The type of entity to restrict to. Default: all + * @param string $subtype The subtype of entity to restrict to. Default: all + * @param string $action_type The type of river action to restrict to. Default: all + * @param int $limit The number of items to retrieve. Default: 20 + * @param int $posted_min The minimum time period to look at. Default: none + * @param int $posted_max The maximum time period to look at. Default: none + * @param bool $pagination Show pagination? + * + * @return string Human-readable river. + * @deprecated 1.8 Use elgg_list_river() + */ +function elgg_view_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', +$type = '', $subtype = '', $action_type = '', $limit = 20, $posted_min = 0, +$posted_max = 0, $pagination = true) { + elgg_deprecated_notice("elgg_view_river_items deprecated for elgg_list_river", 1.8); + + $river_items = get_river_items($subject_guid, $object_guid, $subject_relationship, + $type, $subtype, $action_type, $limit + 1, $posted_min, $posted_max); + + // Get input from outside world and sanitise it + $offset = (int) get_input('offset', 0); + + // view them + $params = array( + 'items' => $river_items, + 'count' => count($river_items), + 'offset' => $offset, + 'limit' => $limit, + 'pagination' => $pagination, + 'list-class' => 'elgg-list-river', + ); + + return elgg_view('page/components/list', $params); +} + +/** + * Construct and execute the query required for the activity stream. + * + * @deprecated 1.8 This is outdated and uses the systemlog table instead of the river table. + * Don't use it. + */ +function get_activity_stream_data($limit = 10, $offset = 0, $type = "", $subtype = "", +$owner_guid = "", $owner_relationship = "") { + elgg_deprecated_notice("get_activity_stream_data was deprecated", 1.8); + + global $CONFIG; + + $limit = (int)$limit; + $offset = (int)$offset; + + if ($type) { + if (!is_array($type)) { + $type = array(sanitise_string($type)); + } else { + foreach ($type as $k => $v) { + $type[$k] = sanitise_string($v); + } + } + } + + if ($subtype) { + if (!is_array($subtype)) { + $subtype = array(sanitise_string($subtype)); + } else { + foreach ($subtype as $k => $v) { + $subtype[$k] = sanitise_string($v); + } + } + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + foreach ($owner_guid as $k => $v) { + $owner_guid[$k] = (int)$v; + } + } else { + $owner_guid = array((int)$owner_guid); + } + } + + $owner_relationship = sanitise_string($owner_relationship); + + // Get a list of possible views + $activity_events = array(); + $activity_views = array_merge(elgg_view_tree('activity', 'default'), + elgg_view_tree('river', 'default')); + + $done = array(); + + foreach ($activity_views as $view) { + $fragments = explode('/', $view); + $tmp = explode('/', $view, 2); + $tmp = $tmp[1]; + + if ((isset($fragments[0])) && (($fragments[0] == 'river') || ($fragments[0] == 'activity')) + && (!in_array($tmp, $done))) { + + if (isset($fragments[1])) { + $f = array(); + for ($n = 1; $n < count($fragments); $n++) { + $val = sanitise_string($fragments[$n]); + switch($n) { + case 1: $key = 'type'; break; + case 2: $key = 'subtype'; break; + case 3: $key = 'event'; break; + } + $f[$key] = $val; + } + + // Filter result based on parameters + $add = true; + if ($type) { + if (!in_array($f['type'], $type)) { + $add = false; + } + } + if (($add) && ($subtype)) { + if (!in_array($f['subtype'], $subtype)) { + $add = false; + } + } + if (($add) && ($event)) { + if (!in_array($f['event'], $event)) { + $add = false; + } + } + + if ($add) { + $activity_events[] = $f; + } + } + + $done[] = $tmp; + } + } + + $n = 0; + foreach ($activity_events as $details) { + // Get what we're talking about + if ($details['subtype'] == 'default') { + $details['subtype'] = ''; + } + + if (($details['type']) && ($details['event'])) { + if ($n > 0) { + $obj_query .= " or "; + } + + $access = ""; + if ($details['type'] != 'relationship') { + $access = " and " . get_access_sql_suffix('sl'); + } + + $obj_query .= "( sl.object_type='{$details['type']}' + AND sl.object_subtype='{$details['subtype']}' + AND sl.event='{$details['event']}' $access )"; + + $n++; + } + } + + // User + if ((count($owner_guid)) && ($owner_guid[0] != 0)) { + $user = " and sl.performed_by_guid in (" . implode(',', $owner_guid) . ")"; + + if ($owner_relationship) { + $friendsarray = ""; + if ($friends = elgg_get_entities_from_relationship(array( + 'relationship' => $owner_relationship, + 'relationship_guid' => $owner_guid[0], + 'inverse_relationship' => FALSE, + 'type' => 'user', + 'subtype' => $subtype, + 'limit' => false)) + ) { + + $friendsarray = array(); + foreach ($friends as $friend) { + $friendsarray[] = $friend->getGUID(); + } + + $user = " and sl.performed_by_guid in (" . implode(',', $friendsarray) . ")"; + } + } + } + + $query = "SELECT sl.* FROM {$CONFIG->dbprefix}system_log sl + WHERE 1 $user AND ($obj_query) + ORDER BY sl.time_created desc limit $offset, $limit"; + return get_data($query); +} + +/** + * Perform standard authentication with a given username and password. + * Returns an ElggUser object for use with login. + * + * @see login + * + * @param string $username The username, optionally (for standard logins) + * @param string $password The password, optionally (for standard logins) + * + * @return ElggUser|false The authenticated user object, or false on failure. + * + * @deprecated 1.8 Use elgg_authenticate + */ +function authenticate($username, $password) { + elgg_deprecated_notice('authenticate() has been deprecated for elgg_authenticate()', 1.8); + $pam = new ElggPAM('user'); + $credentials = array('username' => $username, 'password' => $password); + $result = $pam->authenticate($credentials); + if ($result) { + return get_user_by_username($username); + } + return false; +} + + +/** + * Get the members of a site. + * + * @param int $site_guid Site GUID + * @param int $limit User GUID + * @param int $offset Offset + * + * @return mixed + * @deprecated 1.8 Use ElggSite::getMembers() + */ +function get_site_members($site_guid, $limit = 10, $offset = 0) { + elgg_deprecated_notice("get_site_members() deprecated. + Use ElggSite::getMembers()", 1.8); + + $site = get_entity($site_guid); + if ($site) { + return $site->getMembers($limit, $offset); + } + + return false; +} + +/** + * Display a list of site members + * + * @param int $site_guid The GUID of the site + * @param int $limit The number of members to display on a page + * @param bool $fullview Whether or not to display the full view (default: true) + * + * @return string A displayable list of members + * @deprecated 1.8 Use ElggSite::listMembers() + */ +function list_site_members($site_guid, $limit = 10, $fullview = true) { + elgg_deprecated_notice("list_site_members() deprecated. + Use ElggSite::listMembers()", 1.8); + + $options = array( + 'limit' => $limit, + 'full_view' => $full_view, + ); + + $site = get_entity($site_guid); + if ($site) { + return $site->listMembers($options); + } + + return ''; +} + + +/** + * Add a collection to a site. + * + * @param int $site_guid Site GUID + * @param int $collection_guid Collection GUID + * + * @return mixed + * @deprecated 1.8 Don't use this. + */ +function add_site_collection($site_guid, $collection_guid) { + elgg_deprecated_notice("add_site_collection has been deprecated", 1.8); + global $CONFIG; + + $site_guid = (int)$site_guid; + $collection_guid = (int)$collection_guid; + + return add_entity_relationship($collection_guid, "member_of_site", $site_guid); +} + +/** + * Remove a collection from a site. + * + * @param int $site_guid Site GUID + * @param int $collection_guid Collection GUID + * + * @return mixed + * @deprecated 1.8 Don't use this. + */ +function remove_site_collection($site_guid, $collection_guid) { + elgg_deprecated_notice("remove_site_collection has been deprecated", 1.8); + $site_guid = (int)$site_guid; + $collection_guid = (int)$collection_guid; + + return remove_entity_relationship($collection_guid, "member_of_site", $site_guid); +} + +/** + * Get the collections belonging to a site. + * + * @param int $site_guid Site GUID + * @param string $subtype Subtype + * @param int $limit Limit + * @param int $offset Offset + * + * @return mixed + * @deprecated 1.8 Don't use this. + */ +function get_site_collections($site_guid, $subtype = "", $limit = 10, $offset = 0) { + elgg_deprecated_notice("get_site_collections has been deprecated", 1.8); + $site_guid = (int)$site_guid; + $subtype = sanitise_string($subtype); + $limit = (int)$limit; + $offset = (int)$offset; + + // collection isn't a valid type. This won't work. + return elgg_get_entities_from_relationship(array( + 'relationship' => 'member_of_site', + 'relationship_guid' => $site_guid, + 'inverse_relationship' => TRUE, + 'type' => 'collection', + 'subtype' => $subtype, + 'limit' => $limit, + 'offset' => $offset + )); +} + +/** + * Get an array of tags with weights for use with the output/tagcloud view. + * + * @deprecated 1.8 Use elgg_get_tags(). + * + * @param int $threshold Get the threshold of minimum number of each tags to + * bother with (ie only show tags where there are more + * than $threshold occurances) + * @param int $limit Number of tags to return + * @param string $metadata_name Optionally, the name of the field you want to grab for + * @param string $entity_type Optionally, the entity type ('object' etc) + * @param string $entity_subtype The entity subtype, optionally + * @param int $owner_guid The GUID of the tags owner, optionally + * @param int $site_guid Optionally, the site to restrict to (default is the current site) + * @param int $start_ts Optionally specify a start timestamp for tags used to + * generate cloud. + * @param int $end_ts Optionally specify an end timestamp for tags used to generate cloud + * + * @return array|false Array of objects with ->tag and ->total values, or false on failure + */ +function get_tags($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", +$entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { + + elgg_deprecated_notice('get_tags() has been replaced by elgg_get_tags()', 1.8); + + if (is_array($metadata_name)) { + return false; + } + + $options = array(); + if ($metadata_name === '') { + $options['tag_names'] = array(); + } else { + $options['tag_names'] = array($metadata_name); + } + + $options['threshold'] = $threshold; + $options['limit'] = $limit; + + // rewrite owner_guid to container_guid to emulate old functionality + $container_guid = $owner_guid; + if ($container_guid) { + $options['container_guids'] = $container_guid; + } + + if ($entity_type) { + $options['type'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + if ($site_guid != -1) { + $options['site_guids'] = $site_guid; + } + + if ($end_ts) { + $options['created_time_upper'] = $end_ts; + } + + if ($start_ts) { + $options['created_time_lower'] = $start_ts; + } + + $r = elgg_get_tags($options); + return $r; +} + +/** + * Loads and displays a tagcloud given particular criteria. + * + * @deprecated 1.8 use elgg_view_tagcloud() + * + * @param int $threshold Get the threshold of minimum number of each tags + * to bother with (ie only show tags where there are + * more than $threshold occurances) + * @param int $limit Number of tags to return + * @param string $metadata_name Optionally, the name of the field you want to grab for + * @param string $entity_type Optionally, the entity type ('object' etc) + * @param string $entity_subtype The entity subtype, optionally + * @param int $owner_guid The GUID of the tags owner, optionally + * @param int $site_guid Optionally, the site to restrict to (default is the current site) + * @param int $start_ts Optionally specify a start timestamp for tags used to + * generate cloud. + * @param int $end_ts Optionally specify an end timestamp for tags used to generate + * cloud. + * + * @return string The HTML (or other, depending on view type) of the tagcloud. + */ +function display_tagcloud($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", +$entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { + + elgg_deprecated_notice('display_tagcloud() was deprecated by elgg_view_tagcloud()!', 1.8); + + $tags = get_tags($threshold, $limit, $metadata_name, $entity_type, + $entity_subtype, $owner_guid, $site_guid, $start_ts, $end_ts); + + return elgg_view('output/tagcloud', array( + 'value' => $tags, + 'type' => $entity_type, + 'subtype' => $entity_subtype, + )); +} + + +/** + * Obtains a list of objects owned by a user + * + * @param int $user_guid The GUID of the owning user + * @param string $subtype Optionally, the subtype of objects + * @param int $limit The number of results to return (default 10) + * @param int $offset Indexing offset, if any + * @param int $timelower The earliest time the entity can have been created. Default: all + * @param int $timeupper The latest time the entity can have been created. Default: all + * + * @return false|array An array of ElggObjects or false, depending on success + * @deprecated 1.8 Use elgg_get_entities() instead + */ +function get_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$offset = 0, $timelower = 0, $timeupper = 0) { + elgg_deprecated_notice("get_user_objects() was deprecated in favor of elgg_get_entities()", 1.8); + $ntt = elgg_get_entities(array( + 'type' => 'object', + 'subtype' => $subtype, + 'owner_guid' => $user_guid, + 'limit' => $limit, + 'offset' => $offset, + 'container_guid' => $user_guid, + 'created_time_lower' => $timelower, + 'created_time_upper' => $timeupper + )); + return $ntt; +} + +/** + * Counts the objects (optionally of a particular subtype) owned by a user + * + * @param int $user_guid The GUID of the owning user + * @param string $subtype Optionally, the subtype of objects + * @param int $timelower The earliest time the entity can have been created. Default: all + * @param int $timeupper The latest time the entity can have been created. Default: all + * + * @return int The number of objects the user owns (of this subtype) + * @deprecated 1.8 Use elgg_get_entities() instead + */ +function count_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $timelower = 0, +$timeupper = 0) { + elgg_deprecated_notice("count_user_objects() was deprecated in favor of elgg_get_entities()", 1.8); + $total = elgg_get_entities(array( + 'type' => 'object', + 'subtype' => $subtype, + 'owner_guid' => $user_guid, + 'count' => TRUE, + 'container_guid' => $user_guid, + 'created_time_lower' => $timelower, + 'created_time_upper' => $timeupper + )); + return $total; +} + +/** + * Displays a list of user objects of a particular subtype, with navigation. + * + * @see elgg_view_entity_list + * + * @param int $user_guid The GUID of the user + * @param string $subtype The object subtype + * @param int $limit The number of entities to display on a page + * @param bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view (default: true) + * @param bool $pagination Whether to display pagination (default: true) + * @param int $timelower The earliest time the entity can have been created. Default: all + * @param int $timeupper The latest time the entity can have been created. Default: all + * + * @return string The list in a form suitable to display + * @deprecated 1.8 Use elgg_list_entities() instead + */ +function list_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$fullview = true, $listtypetoggle = true, $pagination = true, $timelower = 0, $timeupper = 0) { + elgg_deprecated_notice("list_user_objects() was deprecated in favor of elgg_list_entities()", 1.8); + + $offset = (int) get_input('offset'); + $limit = (int) $limit; + $count = (int) count_user_objects($user_guid, $subtype, $timelower, $timeupper); + $entities = get_user_objects($user_guid, $subtype, $limit, $offset, $timelower, $timeupper); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, + $pagination); +} + + +/** + * Get user objects by an array of metadata + * + * @param int $user_guid The GUID of the owning user + * @param string $subtype Optionally, the subtype of objects + * @param array $metadata An array of metadata + * @param int $limit The number of results to return (default 10) + * @param int $offset Indexing offset, if any + * + * @return false|array An array of ElggObjects or false, depending on success + * @deprecated 1.8 Use elgg_get_entities_from_metadata() instead + */ +function get_user_objects_by_metadata($user_guid, $subtype = "", $metadata = array(), +$limit = 0, $offset = 0) { + elgg_deprecated_notice("get_user_objects_by_metadata() was deprecated in favor of elgg_get_entities_from_metadata()", 1.8); + return get_entities_from_metadata_multi($metadata, "object", $subtype, $user_guid, + $limit, $offset); +} + +/** + * Set the validation status for a user. + * + * @param bool $status Validated (true) or false + * @param string $method Optional method to say how a user was validated + * @return bool + * @deprecated 1.8 Use elgg_set_user_validation_status() + */ +function set_user_validation_status($user_guid, $status, $method = '') { + elgg_deprecated_notice("set_user_validation_status() is deprecated", 1.8); + return elgg_set_user_validation_status($user_guid, $status, $method); +} + +/** + * Trigger an event requesting that a user guid be validated somehow - either by email address or some other way. + * + * This function invalidates any existing validation value. + * + * @param int $user_guid User's GUID + * @deprecated 1.8 Hook into the register, user plugin hook and request validation. + */ +function request_user_validation($user_guid) { + elgg_deprecated_notice("request_user_validation() is deprecated. + Plugins should register for the 'register, user' plugin hook", 1.8); + $user = get_entity($user_guid); + + if (($user) && ($user instanceof ElggUser)) { + // invalidate any existing validations + set_user_validation_status($user_guid, false); + + // request validation + trigger_elgg_event('validate', 'user', $user); + } +} + +/** + * Register a user settings page with the admin panel. + * This function extends the view "usersettings/main" with the provided view. + * This view should provide a description and either a control or a link to. + * + * Usage: + * - To add a control to the main admin panel then extend usersettings/main + * - To add a control to a new page create a page which renders a view + * usersettings/subpage (where subpage is your new page - + * nb. some pages already exist that you can extend), extend the main view + * to point to it, and add controls to your new view. + * + * At the moment this is essentially a wrapper around elgg_extend_view(). + * + * @param string $new_settings_view The view associated with the control you're adding + * @param string $view The view to extend, by default this is 'usersettings/main'. + * @param int $priority Optional priority to govern the appearance in the list. + * + * @return bool + * @deprecated 1.8 Extend one of the views in core/settings + */ +function extend_elgg_settings_page($new_settings_view, $view = 'usersettings/main', +$priority = 500) { + // see views: /core/settings + elgg_deprecated_notice("extend_elgg_settings_page has been deprecated. Extend one of the settings views instead", 1.8); + + return elgg_extend_view($view, $new_settings_view, $priority); +} + +/** + * Returns a representation of a full 'page' (which might be an HTML page, + * RSS file, etc, depending on the current viewtype) + * + * @param string $title + * @param string $body + * @return string + * + * @deprecated 1.8 Use elgg_view_page() + */ +function page_draw($title, $body, $sidebar = "") { + elgg_deprecated_notice("page_draw() was deprecated in favor of elgg_view_page() in 1.8.", 1.8); + + $vars = array( + 'sidebar' => $sidebar + ); + echo elgg_view_page($title, $body, 'default', $vars); +} + +/** + * Wrapper function to display search listings. + * + * @param string $icon The icon for the listing + * @param string $info Any information that needs to be displayed. + * + * @return string The HTML (etc) representing the listing + * @deprecated 1.8 use elgg_view_image_block() + */ +function elgg_view_listing($icon, $info) { + elgg_deprecated_notice('elgg_view_listing deprecated by elgg_view_image_block', 1.8); + return elgg_view('page/components/image_block', array('image' => $icon, 'body' => $info)); +} + +/** + * Return the icon URL for an entity. + * + * @tip Can be overridden by registering a plugin hook for entity:icon:url, $entity_type. + * + * @internal This is passed an entity rather than a guid to handle non-created entities. + * + * @param ElggEntity $entity The entity + * @param string $size Icon size + * + * @return string URL to the entity icon. + * @deprecated 1.8 Use $entity->getIconURL() + */ +function get_entity_icon_url(ElggEntity $entity, $size = 'medium') { + elgg_deprecated_notice("get_entity_icon_url() deprecated for getIconURL()", 1.8); + 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 + $params = array('entity' => $entity, 'viewtype' => $viewtype, 'size' => $size); + $url = elgg_trigger_plugin_hook('entity:icon:url', $entity->getType(), $params, $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 = "_graphics/icons/default/$size.png"; + } + + return elgg_normalize_url($url); +} + +/** + * Return the current logged in user, or NULL if no user is logged in. + * + * If no user can be found in the current session, a plugin + * hook - 'session:get' 'user' to give plugin authors another + * way to provide user details to the ACL system without touching the session. + * + * @deprecated 1.8 Use elgg_get_logged_in_user_entity() + * @return ElggUser|NULL + */ +function get_loggedin_user() { + elgg_deprecated_notice('get_loggedin_user() is deprecated by elgg_get_logged_in_user_entity()', 1.8); + return elgg_get_logged_in_user_entity(); +} + +/** + * Return the current logged in user by id. + * + * @deprecated 1.8 Use elgg_get_logged_in_user_guid() + * @see elgg_get_logged_in_user_entity() + * @return int + */ +function get_loggedin_userid() { + elgg_deprecated_notice('get_loggedin_userid() is deprecated by elgg_get_logged_in_user_guid()', 1.8); + return elgg_get_logged_in_user_guid(); +} + + +/** + * Returns whether or not the user is currently logged in + * + * @deprecated 1.8 Use elgg_is_logged_in(); + * @return bool + */ +function isloggedin() { + elgg_deprecated_notice('isloggedin() is deprecated by elgg_is_logged_in()', 1.8); + return elgg_is_logged_in(); +} + +/** + * Returns whether or not the user is currently logged in and that they are an admin user. + * + * @deprecated 1.8 Use elgg_is_admin_logged_in() + * @return bool + */ +function isadminloggedin() { + elgg_deprecated_notice('isadminloggedin() is deprecated by elgg_is_admin_logged_in()', 1.8); + return elgg_is_admin_logged_in(); +} + + +/** + * Loads plugins + * + * @deprecated 1.8 Use elgg_load_plugins() + * + * @return bool + */ +function load_plugins() { + elgg_deprecated_notice('load_plugins() is deprecated by elgg_load_plugins()', 1.8); + return elgg_load_plugins(); +} + +/** + * Find the plugin settings for a user. + * + * @param string $plugin_id Plugin name. + * @param int $user_guid The guid who's settings to retrieve. + * + * @deprecated 1.8 Use elgg_get_all_plugin_user_settings() or ElggPlugin->getAllUserSettings() + * @return StdClass Object with all user settings. + */ +function find_plugin_usersettings($plugin_id = null, $user_guid = 0) { + elgg_deprecated_notice('find_plugin_usersettings() is deprecated by elgg_get_all_plugin_user_settings()', 1.8); + return elgg_get_all_plugin_user_settings($user_guid, $plugin_id, true); +} + +/** + * Set a user specific setting for a plugin. + * + * @param string $name The name - note, can't be "title". + * @param mixed $value The value. + * @param int $user_guid Optional user. + * @param string $plugin_id Optional plugin name, if not specified then it + * is detected from where you are calling from. + * + * @return bool + * @deprecated 1.8 Use elgg_set_plugin_user_setting() or ElggPlugin->setUserSetting() + */ +function set_plugin_usersetting($name, $value, $user_guid = 0, $plugin_id = "") { + elgg_deprecated_notice('find_plugin_usersettings() is deprecated by elgg_get_all_plugin_user_settings()', 1.8); + return elgg_set_plugin_user_setting($name, $value, $user_guid, $plugin_id); +} + +/** + * Clears a user-specific plugin setting + * + * @param str $name Name of the plugin setting + * @param int $user_guid Defaults to logged in user + * @param str $plugin_id Defaults to contextual plugin name + * + * @deprecated 1.8 Use elgg_unset_plugin_user_setting or ElggPlugin->unsetUserSetting(). + * @return bool Success + */ +function clear_plugin_usersetting($name, $user_guid = 0, $plugin_id = '') { + elgg_deprecated_notice('clear_plugin_usersetting() is deprecated by elgg_unset_plugin_usersetting()', 1.8); + return elgg_unset_plugin_user_setting($name, $user_guid, $plugin_id); +} + +/** + * Get a user specific setting for a plugin. + * + * @param string $name The name. + * @param int $user_guid Guid of owning user + * @param string $plugin_id Optional plugin name, if not specified + * it is detected from where you are calling. + * + * @deprecated 1.8 Use elgg_get_plugin_user_setting() or ElggPlugin->getUserSetting() + * @return mixed + */ +function get_plugin_usersetting($name, $user_guid = 0, $plugin_id = "") { + elgg_deprecated_notice('get_plugin_usersetting() is deprecated by elgg_get_plugin_user_setting()', 1.8); + return elgg_get_plugin_user_setting($name, $user_guid, $plugin_id); +} + +/** + * Set a setting for a plugin. + * + * @param string $name The name - note, can't be "title". + * @param mixed $value The value. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @deprecated 1.8 Use elgg_set_plugin_setting() or ElggPlugin->setSetting() + * @return int|false + */ +function set_plugin_setting($name, $value, $plugin_id = null) { + elgg_deprecated_notice('set_plugin_setting() is deprecated by elgg_set_plugin_setting()', 1.8); + return elgg_set_plugin_setting($name, $value, $plugin_id); +} + +/** + * Get setting for a plugin. + * + * @param string $name The name. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @deprecated 1.8 Use elgg_get_plugin_setting() or ElggPlugin->getSetting() + * @return mixed + */ +function get_plugin_setting($name, $plugin_id = "") { + elgg_deprecated_notice('get_plugin_setting() is deprecated by elgg_get_plugin_setting()', 1.8); + return elgg_get_plugin_setting($name, $plugin_id); +} + +/** + * Clear a plugin setting. + * + * @param string $name The name. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @deprecated 1.8 Use elgg_unset_plugin_setting() or ElggPlugin->unsetSetting() + * @return bool + */ +function clear_plugin_setting($name, $plugin_id = "") { + elgg_deprecated_notice('clear_plugin_setting() is deprecated by elgg_unset_plugin_setting()', 1.8); + return elgg_unset_plugin_setting($name, $plugin_id); +} + +/** + * Unsets all plugin settings for a plugin. + * + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @return bool + * @deprecated 1.8 Use elgg_unset_all_plugin_settings() or ElggPlugin->unsetAllSettings() + * @since 1.7.0 + */ +function clear_all_plugin_settings($plugin_id = "") { + elgg_deprecated_notice('clear_all_plugin_settings() is deprecated by elgg_unset_all_plugin_setting()', 1.8); + return elgg_unset_all_plugin_settings($plugin_id); +} + + +/** + * Get a list of annotations for a given object/user/annotation type. + * + * @param int|array $entity_guid GUID to return annotations of (falsey for any) + * @param string $entity_type Type of entity + * @param string $entity_subtype Subtype of entity + * @param string $name Name of annotation + * @param mixed $value Value of annotation + * @param int|array $owner_guid Owner(s) of annotation + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Order annotations by SQL + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * @param int $entity_owner_guid Owner guid for the entity + * + * @return array + * @deprecated 1.8 Use elgg_get_annotations() + */ +function get_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", $timelower = 0, +$timeupper = 0, $entity_owner_guid = 0) { + + elgg_deprecated_notice('get_annotations() is deprecated by elgg_get_annotations()', 1.8); + $options = array(); + + if ($entity_guid) { + $options['guid'] = $entity_guid; + } + + if ($entity_type) { + $options['type'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + if ($name) { + $options['annotation_name'] = $name; + } + + if ($value) { + $options['annotation_value'] = $value; + } + + if ($owner_guid) { + $options['annotation_owner_guid'] = $owner_guid; + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + if ($order_by == 'desc') { + $options['order_by'] = 'n_table.time_created desc'; + } + + if ($timelower) { + $options['annotation_time_lower'] = $timelower; + } + + if ($timeupper) { + $options['annotation_time_upper'] = $timeupper; + } + + if ($entity_owner_guid) { + $options['owner_guid'] = $entity_owner_guid; + } + + return elgg_get_annotations($options); +} + + +/** + * Returns a human-readable list of annotations on a particular entity. + * + * @param int $entity_guid The entity GUID + * @param string $name The name of the kind of annotation + * @param int $limit The number of annotations to display at once + * @param true|false $asc Display annotations in ascending order. (Default: true) + * + * @return string HTML (etc) version of the annotation list + * @deprecated 1.8 Use elgg_list_annotations() + */ +function list_annotations($entity_guid, $name = "", $limit = 25, $asc = true) { + elgg_deprecated_notice('list_annotations() is deprecated by elgg_list_annotations()', 1.8); + + if ($asc) { + $asc = "asc"; + } else { + $asc = "desc"; + } + + $options = array( + 'guid' => $entity_guid, + 'limit' => $limit, + 'order_by' => "n_table.time_created $asc" + ); + + return elgg_list_annotations($options); +} + +/** + * Helper function to deprecate annotation calculation functions. Don't use. + * + * @param unknown_type $entity_guid + * @param unknown_type $entity_type + * @param unknown_type $entity_subtype + * @param unknown_type $name + * @param unknown_type $value + * @param unknown_type $value_type + * @param unknown_type $owner_guid + * @param unknown_type $timelower + * @param unknown_type $timeupper + * @param unknown_type $calculation + * @internal Don't use this at all. + * @deprecated 1.8 Use elgg_get_annotations() + */ +function elgg_deprecated_annotation_calculation($entity_guid = 0, $entity_type = "", $entity_subtype = "", +$name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, +$timeupper = 0, $calculation = '') { + + $options = array('annotation_calculation' => $calculation); + + if ($entity_guid) { + $options['guid'] = $entity_guid; + } + + if ($entity_type) { + $options['type'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + if ($name) { + $options['annotation_name'] = $name; + } + + if ($value) { + $options['annotation_value'] = $value; + } + + if ($owner_guid) { + $options['annotation_owner_guid'] = $owner_guid; + } + + if ($order_by == 'desc') { + $options['order_by'] = 'n_table.time_created desc'; + } + + if ($timelower) { + $options['annotation_time_lower'] = $timelower; + } + + if ($timeupper) { + $options['annotation_time_upper'] = $timeupper; + } + + return elgg_get_annotations($options); +} + +/** + * Count the number of annotations based on search parameters + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'count' => true + * @return int + */ +function count_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", +$name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, +$timeupper = 0) { + elgg_deprecated_notice('count_annotations() is deprecated by elgg_get_annotations() and passing "count" => true', 1.8); + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'count'); +} + +/** + * Return the sum of a given integer annotation. + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'sum' + * @return int + */ +function get_annotations_sum($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { + elgg_deprecated_notice('get_annotations_sum() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "sum"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'sum'); +} + +/** + * Return the max of a given integer annotation. + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'max' + * @return int + */ +function get_annotations_max($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { + elgg_deprecated_notice('get_annotations_max() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "max"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'max'); +} + + +/** + * Return the minumum of a given integer annotation. + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'min' + * @return int + */ +function get_annotations_min($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { + elgg_deprecated_notice('get_annotations_min() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "min"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'min'); +} + + +/** + * Return the average of a given integer annotation. + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'min' + * + * @return int + */ +function get_annotations_avg($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { + elgg_deprecated_notice('get_annotations_avg() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "avg"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'avg'); +} + + +/** + * Perform a mathmatical calculation on integer annotations. + * + * @param string $sum What sort of calculation to perform + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * + * @return int + * @deprecated 1.8 Use elgg_get_annotations() and pass anntoation_calculation => <calculation> + */ +function get_annotations_calculate_x($sum = "avg", $entity_guid, $entity_type = "", +$entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0, +$timelower = 0, $timeupper = 0) { + elgg_deprecated_notice('get_annotations_calculate_x() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "calculation"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, $sum); +} + + +/** + * Lists entities by the totals of a particular kind of annotation AND + * the value of a piece of metadata + * + * @param string $entity_type Type of entity. + * @param string $entity_subtype Subtype of entity. + * @param string $name Name of annotation. + * @param string $mdname Metadata name + * @param string $mdvalue Metadata value + * @param int $limit Maximum number of results to return. + * @param int $owner_guid Owner. + * @param int $group_guid Group container. Currently only supported if entity_type is object + * @param boolean $asc Whether to list in ascending or descending order (default: desc) + * @param boolean $fullview Whether to display the entities in full + * @param boolean $listtypetoggle Can the 'gallery' view can be displayed (default: no) + * @param boolean $pagination Display pagination + * @param string $orderdir 'desc' or 'asc' + * + * @deprecated 1.8 Use elgg_list_entities_from_annotation_calculation(). + * + * @return string Formatted entity list + */ +function list_entities_from_annotation_count_by_metadata($entity_type = "", $entity_subtype = "", +$name = "", $mdname = '', $mdvalue = '', $limit = 10, $owner_guid = 0, $group_guid = 0, +$asc = false, $fullview = true, $listtypetoggle = false, $pagination = true, $orderdir = 'desc') { + + $msg = 'list_entities_from_annotation_count_by_metadata() is deprecated by elgg_list_entities_from_annotation_calculation().'; + + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + $options['calculation'] = 'sum'; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($mdname) { + $options['metadata_name'] = $mdname; + } + + if ($mdvalue) { + $options['metadata_value'] = $mdvalue; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + $options['full_view'] = $fullview; + + $options['list_type_toggle'] = $listtypetoggle; + + $options['pagination'] = $pagination; + + $options['limit'] = $limit; + + $options['order_by'] = "annotation_calculation $orderdir"; + + return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Set an alternative base location for a view (as opposed to the default of $CONFIG->viewpath) + * + * @param string $view The name of the view + * @param string $location The base location path + * + * @deprecated 1.8 Use elgg_set_view_location() + */ +function set_view_location($view, $location, $viewtype = '') { + elgg_deprecated_notice("set_view_location() was deprecated by elgg_set_view_location()", 1.8); + return elgg_set_view_location($view, $location, $viewtype); +} + +/** + * Sets the URL handler for a particular entity type and subtype + * + * @param string $function_name The function to register + * @param string $entity_type The entity type + * @param string $entity_subtype The entity subtype + * @return true|false Depending on success + * + * @deprecated 1.8 Use elgg_register_entity_url_handler() + */ +function register_entity_url_handler($function_name, $entity_type = "all", $entity_subtype = "all") { + elgg_deprecated_notice("register_entity_url_handler() was deprecated by elgg_register_entity_url_handler()", 1.8); + return elgg_register_entity_url_handler($entity_type, $entity_subtype, $function_name); +} + + +/** + * Get the metadata where the entities they are referring to match a given criteria. + * + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any + * + * @return mixed + * @deprecated 1.8 Use elgg_get_metadata() + */ +function find_metadata($meta_name = "", $meta_value = "", $entity_type = "", $entity_subtype = "", + $limit = 10, $offset = 0, $order_by = "", $site_guid = 0) { + + elgg_deprecated_notice('get_metadata() is deprecated by elgg_get_metadata()', 1.8); + + $options = array(); + + if ($meta_name) { + $options['annotation_name'] = $meta_name; + } + + if ($meta_value) { + $options['annotation_value'] = $meta_value; + } + + if ($entity_type) { + $options['type'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + if ($order_by == 'desc') { + $options['order_by'] = 'n_table.time_created desc'; + } + + if ($site_guid) { + $options['site_guid'] = $site_guid; + } + + return elgg_get_metadata($options); +} + +/** + * Get metadata objects by name. + * + * @param int $entity_guid Entity GUID + * @param string $meta_name Metadata name + * + * @return mixed ElggMetadata object, an array of ElggMetadata or false. + * @deprecated 1.8 Use elgg_get_metadata() + */ +function get_metadata_byname($entity_guid, $meta_name) { + elgg_deprecated_notice('get_metadata_byname() is deprecated by elgg_get_metadata()', 1.8); + + if (!$entity_guid || !$meta_name) { + return false; + } + + $options = array( + 'guid' => $entity_guid, + 'metadata_name' => $meta_name, + 'limit' => 0 + ); + + $md = elgg_get_metadata($options); + + if ($md && count($md) == 1) { + return $md[0]; + } + + return $md; +} + +/** + * Return all the metadata for a given GUID. + * + * @param int $entity_guid Entity GUID + * + * @return mixed + * @deprecated 1.8 Use elgg_get_metadata() + */ +function get_metadata_for_entity($entity_guid) { + elgg_deprecated_notice('get_metadata_for_entity() is deprecated by elgg_get_metadata()', 1.8); + + if (!$entity_guid) { + return false; + } + + $options = array( + 'guid' => $entity_guid, + 'limit' => 0 + ); + + return elgg_get_metadata($options); +} + +/** + * Get a specific metadata object. + * + * @param int $id The id of the metadata being retrieved. + * + * @return mixed False on failure or ElggMetadata + * @deprecated 1.8 Use elgg_get_metadata_from_id() + */ +function get_metadata($id) { + elgg_deprecated_notice('get_metadata() is deprecated by elgg_get_metadata_from_id()', 1.8); + return elgg_get_metadata_from_id($id); +} + +/** + * Clear all the metadata for a given entity, assuming you have access to that entity. + * + * @param int $guid Entity GUID + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function clear_metadata($guid) { + elgg_deprecated_notice('clear_metadata() is deprecated by elgg_delete_metadata()', 1.8); + if (!$guid) { + return false; + } + return elgg_delete_metadata(array('guid' => $guid, 'limit' => 0)); +} + +/** + * Clear all metadata belonging to a given owner_guid + * + * @param int $owner_guid The owner + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function clear_metadata_by_owner($owner_guid) { + elgg_deprecated_notice('clear_metadata() is deprecated by elgg_delete_metadata()', 1.8); + if (!$owner_guid) { + return false; + } + return elgg_delete_metadata(array('metadata_owner_guid' => $owner_guid, 'limit' => 0)); +} + +/** + * Delete a piece of metadata, where the current user has access. + * + * @param int $id The id of metadata to delete. + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function delete_metadata($id) { + elgg_deprecated_notice('delete_metadata() is deprecated by elgg_delete_metadata()', 1.8); + if (!$id) { + return false; + } + return elgg_delete_metadata(array('metadata_id' => $id)); +} + +/** + * Removes metadata on an entity with a particular name, optionally with a given value. + * + * @param int $guid The entity GUID + * @param string $name The name of the metadata + * @param string $value The value of the metadata (useful to remove a single item of a set) + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function remove_metadata($guid, $name, $value = "") { + elgg_deprecated_notice('delete_metadata() is deprecated by elgg_delete_metadata()', 1.8); + + // prevent them from deleting everything + if (!$guid) { + return false; + } + + $options = array( + 'guid' => $guid, + 'metadata_name' => $name, + 'limit' => 0 + ); + + if ($value) { + $options['metadata_value'] = $value; + } + + return elgg_delete_metadata($options); +} + +/** + * Get a specific annotation. + * + * @param int $annotation_id Annotation ID + * + * @return ElggAnnotation + * @deprecated 1.8 Use elgg_get_annotation_from_id() + */ +function get_annotation($annotation_id) { + elgg_deprecated_notice('get_annotation() is deprecated by elgg_get_annotation_from_id()', 1.8); + return elgg_get_annotation_from_id($annotation_id); +} + +/** + * Delete a given annotation. + * + * @param int $id The annotation id + * + * @return bool + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function delete_annotation($id) { + elgg_deprecated_notice('delete_annotation() is deprecated by elgg_delete_annotations()', 1.8); + if (!$id) { + return false; + } + return elgg_delete_annotations(array('annotation_id' => $annotation_id)); +} + +/** + * Clear all the annotations for a given entity, assuming you have access to that metadata. + * + * @param int $guid The entity guid + * @param string $name The name of the annotation to delete. + * + * @return int Number of annotations deleted or false if an error + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function clear_annotations($guid, $name = "") { + elgg_deprecated_notice('clear_annotations() is deprecated by elgg_delete_annotations()', 1.8); + + if (!$guid) { + return false; + } + + $options = array( + 'guid' => $guid, + 'limit' => 0 + ); + + if ($name) { + $options['annotation_name'] = $name; + } + + return elgg_delete_annotations($options); +} + +/** + * Clear all annotations belonging to a given owner_guid + * + * @param int $owner_guid The owner + * + * @return int Number of annotations deleted + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function clear_annotations_by_owner($owner_guid) { + elgg_deprecated_notice('clear_annotations_by_owner() is deprecated by elgg_delete_annotations()', 1.8); + + if (!$owner_guid) { + return false; + } + + $options = array( + 'annotation_owner_guid' => $guid, + 'limit' => 0 + ); + + return elgg_delete_annotations($options); +} + +/** + * Registers a page handler for a particular identifier + * + * For example, you can register a function called 'blog_page_handler' for handler type 'blog' + * Now for all URLs of type http://yoururl/pg/blog/*, the blog_page_handler() function will be called. + * The part of the URL marked with * above will be exploded on '/' characters and passed as an + * array to that function. + * For example, the URL http://yoururl/blog/username/friends/ would result in the call: + * blog_page_handler(array('username','friends'), blog); + * + * Page handler functions should return true or the default page handler will be called. + * + * A request to register a page handler with the same identifier as previously registered + * handler will replace the previous one. + * + * The context is set to the page handler identifier before the registered + * page handler function is called. For the above example, the context is set to 'blog'. + * + * @param string $handler The page type to handle + * @param string $function Your function name + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_page_handler()} + */ +function register_page_handler($handler, $function){ + elgg_deprecated_notice("register_page_handler() was deprecated by elgg_register_page_handler()", 1.8); + return elgg_register_page_handler($handler, $function); +} + +/** + * Unregister a page handler for an identifier + * + * Note: to replace a page handler, call register_page_handler() + * + * @param string $handler The page type identifier + * @since 1.7.2 + * + * @deprecated 1.8 Use {@link elgg_unregister_page_handler()} + */ +function unregister_page_handler($handler) { + elgg_deprecated_notice("unregister_page_handler() was deprecated by elgg_unregister_page_handler()", 1.8); + return elgg_unregister_page_handler($handler); +} + +/** + * Register an annotation url handler. + * + * @param string $function_name The function. + * @param string $extender_name The name, default 'all'. + * + * @deprecated 1.8 Use {@link elgg_register_annotation_url_handler()} + */ +function register_annotation_url_handler($function, $extender_name) { + elgg_deprecated_notice("register_annotation_url_handler() was deprecated by elgg_register_annotation_url_handler()", 1.8); + return elgg_register_annotation_url_handler($extender_name, $function); +} + +/** + * Sets the URL handler for a particular extender type and name. + * It is recommended that you do not call this directly, instead use one of the wrapper functions in the + * subtype files. + * + * @param string $function_name The function to register + * @param string $extender_type Extender type + * @param string $extender_name The name of the extender + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_extender_url_handler()} + */ +function register_extender_url_handler($function, $type = "all", $name = "all") { + elgg_deprecated_notice("register_extender_url_handler() was deprecated by elgg_register_extender_url_handler()", 1.8); + return elgg_register_extender_url_handler($type, $name, $function); +} + +/** + * Registers and entity type and subtype to return in search and other places. + * A description in the elgg_echo languages file of the form item:type:subtype + * is also expected. + * + * @param string $type The type of entity (object, site, user, group) + * @param string $subtype The subtype to register (may be blank) + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_entity_type()} + */ +function register_entity_type($type, $subtype = null) { + elgg_deprecated_notice("register_entity_type() was deprecated by elgg_register_entity_type()", 1.8); + return elgg_register_entity_type($type, $subtype); +} + +/** + * Register a metadata url handler. + * + * @param string $function_name The function. + * @param string $extender_name The name, default 'all'. + * + * @deprecated 1.8 Use {@link elgg_register_metadata_url_handler()} + */ +function register_metadata_url_handler($function, $extender_name = "all") { + return elgg_register_metadata_url_handler($extender_name, $function); +} + +/** + * Sets the URL handler for a particular relationship type + * + * @param string $function_name The function to register + * @param string $relationship_type The relationship type. + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_relationship_url_handler()} + */ +function register_relationship_url_handler($function_name, $relationship_type = "all") { + elgg_deprecated_notice("register_relationship_url_handler() was deprecated by elgg_register_relationship_url_handler()", 1.8); + return elgg_register_relationship_url_handler($relationship_type, $function_name); +} + +/** + * Registers a view to be simply cached + * + * Views cached in this manner must take no parameters and be login agnostic - + * that is to say, they look the same no matter who is logged in (or logged out). + * + * CSS and the basic jS views are automatically cached like this. + * + * @param string $viewname View name + * + * @deprecated 1.8 Use {@link elgg_register_simplecache_view()} + */ +function elgg_view_register_simplecache($viewname) { + elgg_deprecated_notice("elgg_view_register_simplecache() was deprecated by elgg_register_simplecache_view()", 1.8); + return elgg_register_simplecache_view($viewname); +} + +/** + * Regenerates the simple cache. + * + * @param string $viewtype Optional viewtype to regenerate + * @see elgg_view_register_simplecache() + * + * @deprecated 1.8 Use {@link elgg_regenerate_simplecache()} + */ +function elgg_view_regenerate_simplecache($viewtype = NULL) { + elgg_deprecated_notice("elgg_view_regenerate_simplecache() was deprecated by elgg_regenerate_simplecache()", 1.8); + return elgg_regenerate_simplecache($viewtype); +} + +/** + * Enables the simple cache. + * + * @see elgg_view_register_simplecache() + * + * @deprecated 1.8 Use {@link elgg_enable_simplecache()} + */ +function elgg_view_enable_simplecache() { + elgg_deprecated_notice("elgg_view_enable_simplecache() was deprecated by elgg_enable_simplecache()", 1.8); + return elgg_enable_simplecache(); +} + +/** + * Disables the simple cache. + * + * @see elgg_view_register_simplecache() + * + * @deprecated 1.8 Use {@link elgg_disable_simplecache()} + */ +function elgg_view_disable_simplecache() { + elgg_deprecated_notice("elgg_view_disable_simplecache() was deprecated by elgg_disable_simplecache()", 1.8); + return elgg_disable_simplecache(); +} + +// these were internal functions that perhaps can be removed rather than deprecated +/** + * @deprecated 1.8 + */ +function is_db_installed() { + elgg_deprecated_notice('is_db_installed() has been deprecated', 1.8); + return true; +} + +/** + * @deprecated 1.8 + */ +function is_installed() { + elgg_deprecated_notice('is_installed() has been deprecated', 1.8); + return true; +} + +/** + * Attempt to authenticate. + * This function will process all registered PAM handlers or stop when the first + * handler fails. A handler fails by either returning false or throwing an + * exception. The advantage of throwing an exception is that it returns a message + * through the global $_PAM_HANDLERS_MSG which can be used in communication with + * a user. The order that handlers are processed is determined by the order that + * they were registered. + * + * If $credentials are provided the PAM handler should authenticate using the + * provided credentials, if not then credentials should be prompted for or + * otherwise retrieved (eg from the HTTP header or $_SESSION). + * + * @param mixed $credentials Mixed PAM handler specific credentials (e.g. username, password) + * @param string $policy - the policy type, default is "user" + * @return bool true if authenticated, false if not. + * + * @deprecated 1.8 See {@link ElggPAM} + */ +function pam_authenticate($credentials = NULL, $policy = "user") { + elgg_deprecated_notice('pam_authenticate has been deprecated for ElggPAM', 1.8); + global $_PAM_HANDLERS, $_PAM_HANDLERS_MSG; + + $_PAM_HANDLERS_MSG = array(); + + $authenticated = false; + + foreach ($_PAM_HANDLERS[$policy] as $k => $v) { + $handler = $v->handler; + $importance = $v->importance; + + try { + // Execute the handler + if ($handler($credentials)) { + // Explicitly returned true + $_PAM_HANDLERS_MSG[$k] = "Authenticated!"; + + $authenticated = true; + } else { + $_PAM_HANDLERS_MSG[$k] = "Not Authenticated."; + + // If this is required then abort. + if ($importance == 'required') { + return false; + } + } + } catch (Exception $e) { + $_PAM_HANDLERS_MSG[$k] = "$e"; + + // If this is required then abort. + if ($importance == 'required') { + return false; + } + } + } + + return $authenticated; +} + + +/** + * When given a widget entity and a new requested location, saves the new location + * and also provides a sensible ordering for all widgets in that column + * + * @param ElggObject $widget The widget entity + * @param int $order The order within the column + * @param int $column The column (1, 2 or 3) + * + * @return bool Depending on success + * @deprecated 1.8 use ElggWidget::move() + */ +function save_widget_location(ElggObject $widget, $order, $column) { + elgg_deprecated_notice('save_widget_location() is deprecated', 1.8); + if ($widget instanceof ElggObject) { + if ($widget->subtype == "widget") { + // If you can't move the widget, don't save a new location + if (!$widget->draggable) { + return false; + } + + // Sanitise the column value + if ($column != 1 || $column != 2 || $column != 3) { + $column = 1; + } + + $widget->column = (int) $column; + + $ordertmp = array(); + $params = array( + 'context' => $widget->context, + 'column' => $column, + ); + + if ($entities = get_entities_from_metadata_multi($params, 'object', 'widget')) { + foreach ($entities as $entity) { + $entityorder = $entity->order; + if ($entityorder < $order) { + $ordertmp[$entityorder] = $entity; + } + if ($entityorder >= $order) { + $ordertmp[$entityorder + 10000] = $entity; + } + } + } + + $ordertmp[$order] = $widget; + ksort($ordertmp); + + $orderticker = 10; + foreach ($ordertmp as $orderval => $entity) { + $entity->order = $orderticker; + $orderticker += 10; + } + + return true; + } else { + register_error($widget->subtype); + } + + } + + return false; +} + +/** + * Get widgets for a particular context and column, in order of display + * + * @param int $user_guid The owner user GUID + * @param string $context The context (profile, dashboard etc) + * @param int $column The column (1 or 2) + * + * @return array|false An array of widget ElggObjects, or false + * @deprecated 1.8 Use elgg_get_widgets() + */ +function get_widgets($user_guid, $context, $column) { + elgg_deprecated_notice('get_widgets is depecated for elgg_get_widgets', 1.8); + $params = array( + 'column' => $column, + 'context' => $context + ); + $widgets = get_entities_from_private_setting_multi($params, "object", + "widget", $user_guid, "", 10000); + + if ($widgets) { + $widgetorder = array(); + foreach ($widgets as $widget) { + $order = $widget->order; + while (isset($widgetorder[$order])) { + $order++; + } + $widgetorder[$order] = $widget; + } + + ksort($widgetorder); + + return $widgetorder; + } + + return false; +} + +/** + * Add a new widget instance + * + * @param int $entity_guid GUID of entity that owns this widget + * @param string $handler The handler for this widget + * @param string $context The page context for this widget + * @param int $order The order to display this widget in + * @param int $column The column to display this widget in (1, 2 or 3) + * @param int $access_id If not specified, it is set to the default access level + * + * @return int|false Widget GUID or false on failure + * @deprecated 1.8 use elgg_create_widget() + */ +function add_widget($entity_guid, $handler, $context, $order = 0, $column = 1, $access_id = null) { + elgg_deprecated_notice('add_widget has been deprecated for elgg_create_widget', 1.8); + if (empty($entity_guid) || empty($context) || empty($handler) || !widget_type_exists($handler)) { + return false; + } + + if ($entity = get_entity($entity_guid)) { + $widget = new ElggWidget; + $widget->owner_guid = $entity_guid; + $widget->container_guid = $entity_guid; + if (isset($access_id)) { + $widget->access_id = $access_id; + } else { + $widget->access_id = get_default_access(); + } + + $guid = $widget->save(); + + // private settings cannot be set until ElggWidget saved + $widget->handler = $handler; + $widget->context = $context; + $widget->column = $column; + $widget->order = $order; + + return $guid; + } + + return false; +} + +/** + * Define a new widget type + * + * @param string $handler The identifier for the widget handler + * @param string $name The name of the widget type + * @param string $description A description for the widget type + * @param string $context A comma-separated list of contexts where this + * widget is allowed (default: 'all') + * @param bool $multiple Whether or not multiple instances of this widget + * are allowed on a single dashboard (default: false) + * @param string $positions A comma-separated list of positions on the page + * (side or main) where this widget is allowed (default: "side,main") + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_register_widget_type + */ +function add_widget_type($handler, $name, $description, $context = "all", +$multiple = false, $positions = "side,main") { + elgg_deprecated_notice("add_widget_type deprecated for elgg_register_widget_type", 1.8); + + return elgg_register_widget_type($handler, $name, $description, $context, $multiple); +} + +/** + * Remove a widget type + * + * @param string $handler The identifier for the widget handler + * + * @return void + * @since 1.7.1 + * @deprecated 1.8 Use elgg_unregister_widget_type + */ +function remove_widget_type($handler) { + elgg_deprecated_notice("remove_widget_type deprecated for elgg_unregister_widget_type", 1.8); + return elgg_unregister_widget_type($handler); +} + +/** + * Determines whether or not widgets with the specified handler have been defined + * + * @param string $handler The widget handler identifying string + * + * @return bool Whether or not those widgets exist + * @deprecated 1.8 Use elgg_is_widget_type + */ +function widget_type_exists($handler) { + elgg_deprecated_notice("widget_type_exists deprecated for elgg_is_widget_type", 1.8); + return elgg_is_widget_type($handler); +} + +/** + * Returns an array of stdClass objects representing the defined widget types + * + * @return array A list of types defined (if any) + * @deprecated 1.8 Use elgg_get_widget_types + */ +function get_widget_types() { + elgg_deprecated_notice("get_widget_types deprecrated for elgg_get_widget_types", 1.8); + return elgg_get_widget_types(); +} + +/** + * Saves a widget's settings (by passing an array of + * (name => value) pairs to save_{$handler}_widget) + * + * @param int $widget_guid The GUID of the widget we're saving to + * @param array $params An array of name => value parameters + * + * @return bool + * @deprecated 1.8 Use elgg_save_widget_settings + */ +function save_widget_info($widget_guid, $params) { + elgg_deprecated_notice("save_widget_info() is deprecated for elgg_save_widget_settings", 1.8); + if ($widget = get_entity($widget_guid)) { + + $subtype = $widget->getSubtype(); + + if ($subtype != "widget") { + return false; + } + $handler = $widget->handler; + if (empty($handler) || !widget_type_exists($handler)) { + return false; + } + + if (!$widget->canEdit()) { + return false; + } + + // Save the params to the widget + if (is_array($params) && sizeof($params) > 0) { + foreach ($params as $name => $value) { + + if (!empty($name) && !in_array($name, array( + 'guid', 'owner_guid', 'site_guid' + ))) { + if (is_array($value)) { + // @todo Handle arrays securely + $widget->setMetaData($name, $value, "", true); + } else { + $widget->$name = $value; + } + } + } + $widget->save(); + } + + $function = "save_{$handler}_widget"; + if (is_callable($function)) { + return $function($params); + } + + return true; + } + + return false; +} + +/** + * Reorders the widgets from a widget panel + * + * @param string $panelstring1 String of guids of ElggWidget objects separated by :: + * @param string $panelstring2 String of guids of ElggWidget objects separated by :: + * @param string $panelstring3 String of guids of ElggWidget objects separated by :: + * @param string $context Profile or dashboard + * @param int $owner Owner guid + * + * @return void + * @deprecated 1.8 Don't use. + */ +function reorder_widgets_from_panel($panelstring1, $panelstring2, $panelstring3, $context, $owner) { + elgg_deprecated_notice("reorder_widgets_from_panel() is deprecated", 1.8); + $return = true; + + $mainwidgets = explode('::', $panelstring1); + $sidewidgets = explode('::', $panelstring2); + $rightwidgets = explode('::', $panelstring3); + + $handlers = array(); + $guids = array(); + + if (is_array($mainwidgets) && sizeof($mainwidgets) > 0) { + foreach ($mainwidgets as $widget) { + + $guid = (int) $widget; + + if ("{$guid}" == "{$widget}") { + $guids[1][] = $widget; + } else { + $handlers[1][] = $widget; + } + } + } + if (is_array($sidewidgets) && sizeof($sidewidgets) > 0) { + foreach ($sidewidgets as $widget) { + + $guid = (int) $widget; + + if ("{$guid}" == "{$widget}") { + $guids[2][] = $widget; + } else { + $handlers[2][] = $widget; + } + + } + } + if (is_array($rightwidgets) && sizeof($rightwidgets) > 0) { + foreach ($rightwidgets as $widget) { + + $guid = (int) $widget; + + if ("{$guid}" == "{$widget}") { + $guids[3][] = $widget; + } else { + $handlers[3][] = $widget; + } + + } + } + + // Reorder existing widgets or delete ones that have vanished + foreach (array(1, 2, 3) as $column) { + if ($dbwidgets = get_widgets($owner, $context, $column)) { + + foreach ($dbwidgets as $dbwidget) { + if (in_array($dbwidget->getGUID(), $guids[1]) + || in_array($dbwidget->getGUID(), $guids[2]) || in_array($dbwidget->getGUID(), $guids[3])) { + + if (in_array($dbwidget->getGUID(), $guids[1])) { + $pos = array_search($dbwidget->getGUID(), $guids[1]); + $col = 1; + } else if (in_array($dbwidget->getGUID(), $guids[2])) { + $pos = array_search($dbwidget->getGUID(), $guids[2]); + $col = 2; + } else { + $pos = array_search($dbwidget->getGUID(), $guids[3]); + $col = 3; + } + $pos = ($pos + 1) * 10; + $dbwidget->column = $col; + $dbwidget->order = $pos; + } else { + $dbguid = $dbwidget->getGUID(); + if (!$dbwidget->delete()) { + $return = false; + } else { + // Remove state cookie + setcookie('widget' + $dbguid, null); + } + } + } + + } + // Add new ones + if (sizeof($guids[$column]) > 0) { + foreach ($guids[$column] as $key => $guid) { + if ($guid == 0) { + $pos = ($key + 1) * 10; + $handler = $handlers[$column][$key]; + if (!add_widget($owner, $handler, $context, $pos, $column)) { + $return = false; + } + } + } + } + } + + return $return; +} + +/** + * Register a particular context for use with widgets. + * + * @param string $context The context we wish to enable context for + * + * @return void + * @deprecated 1.8 Don't use. + */ +function use_widgets($context) { + elgg_deprecated_notice("use_widgets is deprecated", 1.8); + global $CONFIG; + + if (!isset($CONFIG->widgets)) { + $CONFIG->widgets = new stdClass; + } + + if (!isset($CONFIG->widgets->contexts)) { + $CONFIG->widgets->contexts = array(); + } + + if (!empty($context)) { + $CONFIG->widgets->contexts[] = $context; + } +} + +/** + * Determines whether or not the current context is using widgets + * + * @return bool Depending on widget status + * @deprecated 1.8 Don't use. + */ +function using_widgets() { + elgg_deprecated_notice("using_widgets is deprecated", 1.8); + global $CONFIG; + + $context = elgg_get_context(); + if (isset($CONFIG->widgets->contexts) && is_array($CONFIG->widgets->contexts)) { + if (in_array($context, $CONFIG->widgets->contexts)) { + return true; + } + } + + return false; +} + +/** + * Displays a particular widget + * + * @param ElggObject $widget The widget to display + * @return string The HTML for the widget, including JavaScript wrapper + * + * @deprecated 1.8 Use elgg_view_entity() + */ +function display_widget(ElggObject $widget) { + elgg_deprecated_notice("display_widget() was been deprecated. Use elgg_view_entity().", 1.8); + return elgg_view_entity($widget); +} + +/** + * Count the number of comments attached to an entity + * + * @param ElggEntity $entity + * @return int Number of comments + * @deprecated 1.8 Use ElggEntity->countComments() + */ +function elgg_count_comments($entity) { + elgg_deprecated_notice('elgg_count_comments() is deprecated by ElggEntity->countComments()', 1.8); + + if ($entity instanceof ElggEntity) { + return $entity->countComments(); + } + + return 0; +} + +/** + * Removes all items relating to a particular acting entity from the river + * + * @param int $subject_guid The GUID of the entity + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_subject($subject_guid) { + elgg_deprecated_notice("remove_from_river_by_subject() deprecated by elgg_delete_river()", 1.8); + + return elgg_delete_river(array('subject_guid' => $subject_guid)); +} + +/** + * Removes all items relating to a particular entity being acted upon from the river + * + * @param int $object_guid The GUID of the entity + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_object($object_guid) { + elgg_deprecated_notice("remove_from_river_by_object() deprecated by elgg_delete_river()", 1.8); + + return elgg_delete_river(array('object_guid' => $object_guid)); +} + +/** + * Removes all items relating to a particular annotation being acted upon from the river + * + * @param int $annotation_id The ID of the annotation + * + * @return bool Depending on success + * @since 1.7.0 + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_annotation($annotation_id) { + elgg_deprecated_notice("remove_from_river_by_annotation() deprecated by elgg_delete_river()", 1.8); + + return elgg_delete_river(array('annotation_id' => $annotation_id)); +} + +/** + * Removes a single river entry + * + * @param int $id The ID of the river entry + * + * @return bool Depending on success + * @since 1.7.2 + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_id($id) { + elgg_deprecated_notice("remove_from_river_by_id() deprecated by elgg_delete_river()", 1.8); + + return elgg_delete_river(array('id' => $id)); +} + +/** + * A default page handler + * Tries to locate a suitable file to include. Only works for core pages, not plugins. + * + * @param array $page The page URL elements + * @param string $handler The base handler + * + * @return true|false Depending on success + * @deprecated 1.8 + */ +function default_page_handler($page, $handler) { + global $CONFIG; + + elgg_deprecated_notice("default_page_handler is deprecated", "1.8"); + + $page = implode('/', $page); + + // protect against including arbitary files + $page = str_replace("..", "", $page); + + $callpath = $CONFIG->path . $handler . "/" . $page; + if (is_dir($callpath)) { + $callpath = sanitise_filepath($callpath); + $callpath .= "index.php"; + if (file_exists($callpath)) { + if (include($callpath)) { + return TRUE; + } + } + } else if (file_exists($callpath)) { + include($callpath); + return TRUE; + } + + return FALSE; +} + +/** + * Invalidate this class's entry in the cache. + * + * @param int $guid The entity guid + * + * @return void + * @access private + * @deprecated 1.8 + */ +function invalidate_cache_for_entity($guid) { + elgg_deprecated_notice('invalidate_cache_for_entity() is a private function and should not be used.', 1.8); + _elgg_invalidate_cache_for_entity($guid); +} + +/** + * Cache an entity. + * + * Stores an entity in $ENTITY_CACHE; + * + * @param ElggEntity $entity Entity to cache + * + * @return void + * @access private + * @deprecated 1.8 + */ +function cache_entity(ElggEntity $entity) { + elgg_deprecated_notice('cache_entity() is a private function and should not be used.', 1.8); + _elgg_cache_entity($entity); +} + +/** + * Retrieve a entity from the cache. + * + * @param int $guid The guid + * + * @return ElggEntity|bool false if entity not cached, or not fully loaded + * @access private + * @deprecated 1.8 + */ +function retrieve_cached_entity($guid) { + elgg_deprecated_notice('retrieve_cached_entity() is a private function and should not be used.', 1.8); + return _elgg_retrieve_cached_entity($guid); +} diff --git a/engine/lib/deprecated-1.9.php b/engine/lib/deprecated-1.9.php new file mode 100644 index 000000000..31d03428f --- /dev/null +++ b/engine/lib/deprecated-1.9.php @@ -0,0 +1,582 @@ +<?php +/** + * Return a timestamp for the start of a given day (defaults today). + * + * @param int $day Day + * @param int $month Month + * @param int $year Year + * + * @return int + * @access private + * @deprecated 1.9 + */ +function get_day_start($day = null, $month = null, $year = null) { + elgg_deprecated_notice('get_day_start() has been deprecated', 1.9); + return mktime(0, 0, 0, $month, $day, $year); +} + +/** + * Return a timestamp for the end of a given day (defaults today). + * + * @param int $day Day + * @param int $month Month + * @param int $year Year + * + * @return int + * @access private + * @deprecated 1.9 + */ +function get_day_end($day = null, $month = null, $year = null) { + elgg_deprecated_notice('get_day_end() has been deprecated', 1.9); + return mktime(23, 59, 59, $month, $day, $year); +} + +/** + * Return the notable entities for a given time period. + * + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @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 instead of entities. Defaults to false. + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any. + * @param mixed $container_guid Container or containers to get entities from (default: any). + * + * @return array|false + * @access private + * @deprecated 1.9 + */ +function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "asc", $limit = 10, $offset = 0, $count = false, $site_guid = 0, +$container_guid = null) { + elgg_deprecated_notice('get_notable_entities() has been deprecated', 1.9); + global $CONFIG; + + if ($subtype === false || $subtype === null || $subtype === 0) { + return false; + } + + $start_time = (int)$start_time; + $end_time = (int)$end_time; + $order_by = sanitise_string($order_by); + $limit = (int)$limit; + $offset = (int)$offset; + $site_guid = (int) $site_guid; + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + $where = array(); + + if (is_array($type)) { + $tempwhere = ""; + if (sizeof($type)) { + foreach ($type as $typekey => $subtypearray) { + foreach ($subtypearray as $subtypeval) { + $typekey = sanitise_string($typekey); + if (!empty($subtypeval)) { + $subtypeval = (int) get_subtype_id($typekey, $subtypeval); + } else { + $subtypeval = 0; + } + if (!empty($tempwhere)) { + $tempwhere .= " or "; + } + $tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; + } + } + } + if (!empty($tempwhere)) { + $where[] = "({$tempwhere})"; + } + } else { + $type = sanitise_string($type); + $subtype = get_subtype_id($type, $subtype); + + 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[] = "e.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 = implode(",", $owner_guid); + $where[] = "e.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}"; + } + } + + // Add the calendar stuff + $cal_join = " + JOIN {$CONFIG->dbprefix}metadata cal_start on e.guid=cal_start.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_start_name on cal_start.name_id=cal_start_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_start_value on cal_start.value_id=cal_start_value.id + + JOIN {$CONFIG->dbprefix}metadata cal_end on e.guid=cal_end.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_end_name on cal_end.name_id=cal_end_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_end_value on cal_end.value_id=cal_end_value.id + "; + $where[] = "cal_start_name.string='calendar_start'"; + $where[] = "cal_start_value.string>=$start_time"; + $where[] = "cal_end_name.string='calendar_end'"; + $where[] = "cal_end_value.string <= $end_time"; + + + if (!$count) { + $query = "SELECT e.* from {$CONFIG->dbprefix}entities e $cal_join where "; + } else { + $query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e $cal_join where "; + } + foreach ($where as $w) { + $query .= " $w and "; + } + + $query .= get_access_sql_suffix('e'); // Add access controls + + if (!$count) { + $query .= " order by n.calendar_start $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; + } +} + +/** + * Return the notable entities for a given time period based on an item of metadata. + * + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any. + * @param bool $count If true, returns count instead of entities. (Default: false) + * + * @return int|array A list of entities, or a count if $count is set to true + * @access private + * @deprecated 1.9 + */ +function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $meta_value = "", +$entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", +$site_guid = 0, $count = false) { + elgg_deprecated_notice('get_notable_entities_from_metadata() has been deprecated', 1.9); + + global $CONFIG; + + $meta_n = get_metastring_id($meta_name); + $meta_v = get_metastring_id($meta_value); + + $start_time = (int)$start_time; + $end_time = (int)$end_time; + $entity_type = sanitise_string($entity_type); + $entity_subtype = get_subtype_id($entity_type, $entity_subtype); + $limit = (int)$limit; + $offset = (int)$offset; + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + $order_by = sanitise_string($order_by); + $site_guid = (int) $site_guid; + if ((is_array($owner_guid) && (count($owner_guid)))) { + foreach ($owner_guid as $key => $guid) { + $owner_guid[$key] = (int) $guid; + } + } else { + $owner_guid = (int) $owner_guid; + } + + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + //$access = get_access_list(); + + $where = array(); + + if ($entity_type != "") { + $where[] = "e.type='$entity_type'"; + } + + if ($entity_subtype) { + $where[] = "e.subtype=$entity_subtype"; + } + + if ($meta_name != "") { + $where[] = "m.name_id='$meta_n'"; + } + + if ($meta_value != "") { + $where[] = "m.value_id='$meta_v'"; + } + + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + + if (is_array($owner_guid)) { + $where[] = "e.container_guid in (" . implode(",", $owner_guid) . ")"; + } else if ($owner_guid > 0) { + $where[] = "e.container_guid = {$owner_guid}"; + } + + // Add the calendar stuff + $cal_join = " + JOIN {$CONFIG->dbprefix}metadata cal_start on e.guid=cal_start.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_start_name on cal_start.name_id=cal_start_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_start_value on cal_start.value_id=cal_start_value.id + + JOIN {$CONFIG->dbprefix}metadata cal_end on e.guid=cal_end.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_end_name on cal_end.name_id=cal_end_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_end_value on cal_end.value_id=cal_end_value.id + "; + + $where[] = "cal_start_name.string='calendar_start'"; + $where[] = "cal_start_value.string>=$start_time"; + $where[] = "cal_end_name.string='calendar_end'"; + $where[] = "cal_end_value.string <= $end_time"; + + if (!$count) { + $query = "SELECT distinct e.* "; + } else { + $query = "SELECT count(distinct e.guid) as total "; + } + + $query .= "from {$CONFIG->dbprefix}entities e" + . " JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid $cal_join where"; + + foreach ($where as $w) { + $query .= " $w and "; + } + + // Add access controls + $query .= get_access_sql_suffix("e"); + $query .= ' and ' . get_access_sql_suffix("m"); + + if (!$count) { + // Add order and limit + $query .= " order by $order_by limit $offset, $limit"; + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($row = get_data_row($query)) { + return $row->total; + } + } + + return false; +} + +/** + * Return the notable entities for a given time period based on their relationship. + * + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Reverse the normal function of the query to say + * "give me all entities for whom $relationship_guid is a + * $relationship of" + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param string $order_by Optional Order by + * @param int $limit Limit + * @param int $offset Offset + * @param boolean $count If true returns a count of entities (default false) + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private + * @deprecated 1.9 + */ +function get_noteable_entities_from_relationship($start_time, $end_time, $relationship, +$relationship_guid, $inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + elgg_deprecated_notice('get_noteable_entities_from_relationship() has been deprecated', 1.9); + + global $CONFIG; + + $start_time = (int)$start_time; + $end_time = (int)$end_time; + $relationship = sanitise_string($relationship); + $relationship_guid = (int)$relationship_guid; + $inverse_relationship = (bool)$inverse_relationship; + $type = sanitise_string($type); + $subtype = get_subtype_id($type, $subtype); + $owner_guid = (int)$owner_guid; + if ($order_by == "") { + $order_by = "time_created desc"; + } + $order_by = sanitise_string($order_by); + $limit = (int)$limit; + $offset = (int)$offset; + $site_guid = (int) $site_guid; + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + //$access = get_access_list(); + + $where = array(); + + if ($relationship != "") { + $where[] = "r.relationship='$relationship'"; + } + if ($relationship_guid) { + $where[] = $inverse_relationship ? + "r.guid_two='$relationship_guid'" : "r.guid_one='$relationship_guid'"; + } + if ($type != "") { + $where[] = "e.type='$type'"; + } + if ($subtype) { + $where[] = "e.subtype=$subtype"; + } + if ($owner_guid != "") { + $where[] = "e.container_guid='$owner_guid'"; + } + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + + // Add the calendar stuff + $cal_join = " + JOIN {$CONFIG->dbprefix}metadata cal_start on e.guid=cal_start.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_start_name on cal_start.name_id=cal_start_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_start_value on cal_start.value_id=cal_start_value.id + + JOIN {$CONFIG->dbprefix}metadata cal_end on e.guid=cal_end.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_end_name on cal_end.name_id=cal_end_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_end_value on cal_end.value_id=cal_end_value.id + "; + $where[] = "cal_start_name.string='calendar_start'"; + $where[] = "cal_start_value.string>=$start_time"; + $where[] = "cal_end_name.string='calendar_end'"; + $where[] = "cal_end_value.string <= $end_time"; + + // Select what we're joining based on the options + $joinon = "e.guid = r.guid_one"; + if (!$inverse_relationship) { + $joinon = "e.guid = r.guid_two"; + } + + if ($count) { + $query = "SELECT count(distinct e.guid) as total "; + } else { + $query = "SELECT distinct e.* "; + } + $query .= " from {$CONFIG->dbprefix}entity_relationships r" + . " JOIN {$CONFIG->dbprefix}entities e on $joinon $cal_join where "; + + foreach ($where as $w) { + $query .= " $w and "; + } + // Add access controls + $query .= get_access_sql_suffix("e"); + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Get all entities for today. + * + * @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 If true returns a count of entities (default false) + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any + * @param mixed $container_guid Container(s) to get entities from (default: any). + * + * @return array|false + * @access private + * @deprecated 1.9 + */ +function get_todays_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", +$limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = null) { + elgg_deprecated_notice('get_todays_entities() has been deprecated', 1.9); + + $day_start = get_day_start(); + $day_end = get_day_end(); + + return get_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $order_by, + $limit, $offset, $count, $site_guid, $container_guid); +} + +/** + * Get entities for today from metadata. + * + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any. + * @param bool $count If true, returns count instead of entities. (Default: false) + * + * @return int|array A list of entities, or a count if $count is set to true + * @access private + * @deprecated 1.9 + */ +function get_todays_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", +$entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, +$count = false) { + elgg_deprecated_notice('get_todays_entities_from_metadata() has been deprecated', 1.9); + + $day_start = get_day_start(); + $day_end = get_day_end(); + + return get_notable_entities_from_metadata($day_start, $day_end, $meta_name, $meta_value, + $entity_type, $entity_subtype, $owner_guid, $limit, $offset, $order_by, $site_guid, $count); +} + +/** + * Get entities for today from a relationship + * + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Reverse the normal function of the query to say + * "give me all entities for whom $relationship_guid is a + * $relationship of" + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param string $order_by Optional Order by + * @param int $limit Limit + * @param int $offset Offset + * @param boolean $count If true returns a count of entities (default false) + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private + * @deprecated 1.9 + */ +function get_todays_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + elgg_deprecated_notice('get_todays_entities_from_relationship() has been deprecated', 1.9); + + $day_start = get_day_start(); + $day_end = get_day_end(); + + return get_notable_entities_from_relationship($day_start, $day_end, $relationship, + $relationship_guid, $inverse_relationship, $type, $subtype, $owner_guid, $order_by, + $limit, $offset, $count, $site_guid); +} + +/** + * Returns a viewable list of entities for a given time period. + * + * @see elgg_view_entity_list + * + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to return; 10 by default + * @param boolean $fullview Whether or not to display the full view (default: true) + * @param boolean $listtypetoggle Whether or not to allow gallery view + * @param boolean $navigation Display pagination? Default: true + * + * @return string A viewable list of entities + * @access private + * @deprecated 1.9 + */ +function list_notable_entities($start_time, $end_time, $type= "", $subtype = "", $owner_guid = 0, +$limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { + elgg_deprecated_notice('list_notable_entities() has been deprecated', 1.9); + + $offset = (int) get_input('offset'); + $count = get_notable_entities($start_time, $end_time, $type, $subtype, + $owner_guid, "", $limit, $offset, true); + + $entities = get_notable_entities($start_time, $end_time, $type, $subtype, + $owner_guid, "", $limit, $offset); + + return elgg_view_entity_list($entities, $count, $offset, $limit, + $fullview, $listtypetoggle, $navigation); +} + +/** + * Return a list of today's entities. + * + * @see list_notable_entities + * + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to return; 10 by default + * @param boolean $fullview Whether or not to display the full view (default: true) + * @param boolean $listtypetoggle Whether or not to allow gallery view + * @param boolean $navigation Display pagination? Default: true + * + * @return string A viewable list of entities + * @access private + * @deprecated 1.9 + */ +function list_todays_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, +$fullview = true, $listtypetoggle = false, $navigation = true) { + elgg_deprecated_notice('list_todays_entities() has been deprecated', 1.9); + + $day_start = get_day_start(); + $day_end = get_day_end(); + + return list_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $limit, + $fullview, $listtypetoggle, $navigation); +} diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index 9b5b34d86..34111c69d 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -1,1501 +1,546 @@ <?php /** - * Elgg library - * Contains important functionality core to Elgg + * Bootstrapping and helper procedural code available for use in Elgg core and plugins. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @todo These functions can't be subpackaged because they cover a wide mix of + * purposes and subsystems. Many of them should be moved to more relevant files. */ -/** - * Getting directories and moving the browser - */ +// prep core classes to be autoloadable +spl_autoload_register('_elgg_autoload'); +elgg_register_classes(dirname(dirname(__FILE__)) . '/classes'); /** - * Adds messages to the session so they'll be carried over, and forwards the browser. - * Returns false if headers have already been sent and the browser cannot be moved. + * Autoload classes * - * @param string $location URL to forward to browser to - * @return nothing|false - */ -function forward($location = "") { - global $CONFIG; - - if (!headers_sent()) { - $current_page = current_page_url(); - // What is this meant to do? - //if (strpos($current_page, $CONFIG->wwwroot . "action") ===false) - - $_SESSION['msg'] = array_merge($_SESSION['msg'], system_messages()); - if ((substr_count($location, 'http://') == 0) && (substr_count($location, 'https://') == 0)) { - $location = $CONFIG->url . $location; - } - - header("Location: {$location}"); - exit; - } - - return false; -} - -/** - * Return the current page URL. + * @param string $class The name of the class + * + * @return void + * @throws Exception + * @access private */ -function current_page_url() { +function _elgg_autoload($class) { global $CONFIG; - $url = parse_url($CONFIG->wwwroot); - - $page = $url['scheme'] . "://"; - - // user/pass - if ((isset($url['user'])) && ($url['user'])) { - $page .= $url['user']; - } - if ((isset($url['pass'])) && ($url['pass'])) { - $page .= ":".$url['pass']; - } - if ((isset($url['user']) && $url['user']) || - (isset($url['pass']) && $url['pass'])) { - $page .="@"; - } - - $page .= $url['host']; - - if ((isset($url['port'])) && ($url['port'])) { - $page .= ":" . $url['port']; + if (!isset($CONFIG->classes[$class]) || !include($CONFIG->classes[$class])) { + return false; } - - //$page.="/"; - $page = trim($page, "/"); - - $page .= $_SERVER['REQUEST_URI']; - - return $page; } /** - * Templating and visual functionality - */ - -$CURRENT_SYSTEM_VIEWTYPE = ""; - -/** - * Override the view mode detection for the elgg view system. + * Register all files found in $dir as classes + * Need to be named MyClass.php * - * This function will force any further views to be rendered using $viewtype. Remember to call elgg_set_viewtype() with - * no parameters to reset. + * @param string $dir The dir to look in * - * @param string $viewtype The view type, e.g. 'rss', or 'default'. - * @return bool + * @return void + * @since 1.8.0 */ -function elgg_set_viewtype($viewtype = "") { - global $CURRENT_SYSTEM_VIEWTYPE; +function elgg_register_classes($dir) { + $classes = elgg_get_file_list($dir, array(), array(), array('.php')); - $CURRENT_SYSTEM_VIEWTYPE = $viewtype; - - return true; + foreach ($classes as $class) { + elgg_register_class(basename($class, '.php'), $class); + } } /** - * Return the current view type used by the elgg view system. + * Register a classname to a file. * - * By default, this function will return a value based on the default for your system or from the command line - * view parameter. However, you may force a given view type by calling elgg_set_viewtype() + * @param string $class The name of the class + * @param string $location The location of the file * - * @return string The view. + * @return true + * @since 1.8.0 */ -function elgg_get_viewtype() { - global $CURRENT_SYSTEM_VIEWTYPE, $CONFIG; - - $viewtype = NULL; - - if ($CURRENT_SYSTEM_VIEWTYPE != "") { - return $CURRENT_SYSTEM_VIEWTYPE; - } - - if ((empty($_SESSION['view'])) || ( (trim($CONFIG->view!="")) && ($_SESSION['view']!=$CONFIG->view) )) { - $_SESSION['view'] = "default"; - // If we have a config default view for this site then use that instead of 'default' - if (/*(is_installed()) && */(!empty($CONFIG->view)) && (trim($CONFIG->view)!="")) { - $_SESSION['view'] = $CONFIG->view; - } - } +function elgg_register_class($class, $location) { + global $CONFIG; - if (empty($viewtype) && is_callable('get_input')) { - $viewtype = get_input('view'); + if (!isset($CONFIG->classes)) { + $CONFIG->classes = array(); } - if (empty($viewtype)) { - $viewtype = $_SESSION['view']; - } + $CONFIG->classes[$class] = $location; - return $viewtype; + return true; } /** - * Return the location of a given view. + * Register a php library. * - * @param string $view The view. - * @param string $viewtype The viewtype + * @param string $name The name of the library + * @param string $location The location of the file + * + * @return void + * @since 1.8.0 */ -function elgg_get_view_location($view, $viewtype = '') { +function elgg_register_library($name, $location) { global $CONFIG; - if (empty($viewtype)) { - $viewtype = elgg_get_viewtype(); - } - - if (!isset($CONFIG->views->locations[$viewtype][$view])) { - if (!isset($CONFIG->viewpath)) { - return dirname(dirname(dirname(__FILE__))) . "/views/"; - } else { - return $CONFIG->viewpath; - } - } else { - return $CONFIG->views->locations[$viewtype][$view]; + if (!isset($CONFIG->libraries)) { + $CONFIG->libraries = array(); } - return false; + $CONFIG->libraries[$name] = $location; } /** - * Handles templating views + * Load a php library. * - * @see set_template_handler + * @param string $name The name of the library * - * @param string $view The name and location of the view to use - * @param array $vars Any variables that the view requires, passed as an array - * @param boolean $bypass If set to true, elgg_view will bypass any specified alternative template handler; by default, it will hand off to this if requested (see set_template_handler) - * @param boolean $debug If set to true, the viewer will complain if it can't find a view - * @param string $viewtype If set, forces the viewtype for the elgg_view call to be this value (default: standard detection) - * @return string The HTML content + * @return void + * @throws InvalidParameterException + * @since 1.8.0 + * @todo return boolean in 1.9 to indicate whether the library has been loaded */ -function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $viewtype = '') { +function elgg_load_library($name) { global $CONFIG; - static $usercache; - - $view = (string)$view; - - // basic checking for bad paths - if (strpos($view, '..') !== false) { - return false; - } - - $view_orig = $view; - - // Trigger the pagesetup event - if (!isset($CONFIG->pagesetupdone)) { - trigger_elgg_event('pagesetup','system'); - $CONFIG->pagesetupdone = true; - } - - if (!is_array($usercache)) { - $usercache = array(); - } - if (!is_array($vars)) { - elgg_log('Vars in views must be an array!', 'ERROR'); - $vars = array(); - } + static $loaded_libraries = array(); - if (empty($vars)) { - $vars = array(); - } - - // Load session and configuration variables into $vars - // $_SESSION will always be an array if it is set - if (isset($_SESSION) /*&& is_array($_SESSION)*/ ) { - //= array_merge($vars, $_SESSION); - $vars += $_SESSION; - } - - $vars['config'] = array(); - - if (!empty($CONFIG)) { - $vars['config'] = $CONFIG; - } - - $vars['url'] = $CONFIG->url; - - // Load page owner variables into $vars - if (is_callable('page_owner')) { - $vars['page_owner'] = page_owner(); - } else { - $vars['page_owner'] = -1; - } - - if (($vars['page_owner'] != -1) && (is_installed())) { - if (!isset($usercache[$vars['page_owner']])) { - $vars['page_owner_user'] = get_entity($vars['page_owner']); - $usercache[$vars['page_owner']] = $vars['page_owner_user']; - } else { - $vars['page_owner_user'] = $usercache[$vars['page_owner']]; - } + if (in_array($name, $loaded_libraries)) { + return; } - if (!isset($vars['js'])) { - $vars['js'] = ""; + if (!isset($CONFIG->libraries)) { + $CONFIG->libraries = array(); } - // If it's been requested, pass off to a template handler instead - if ($bypass == false && isset($CONFIG->template_handler) && !empty($CONFIG->template_handler)) { - $template_handler = $CONFIG->template_handler; - if (is_callable($template_handler)) { - return $template_handler($view, $vars); - } + if (!isset($CONFIG->libraries[$name])) { + $error = elgg_echo('InvalidParameterException:LibraryNotRegistered', array($name)); + throw new InvalidParameterException($error); } - // Get the current viewtype - if (empty($viewtype)) { - $viewtype = elgg_get_viewtype(); + if (!include_once($CONFIG->libraries[$name])) { + $error = elgg_echo('InvalidParameterException:LibraryNotFound', array( + $name, + $CONFIG->libraries[$name]) + ); + throw new InvalidParameterException($error); } - // Set up any extensions to the requested view - if (isset($CONFIG->views->extensions[$view])) { - $viewlist = $CONFIG->views->extensions[$view]; - } else { - $viewlist = array(500 => $view); - } - // Start the output buffer, find the requested view file, and execute it - ob_start(); - - foreach($viewlist as $priority => $view) { - $view_location = elgg_get_view_location($view, $viewtype); - $view_file = "$view_location$viewtype/$view.php"; - $default_view_file = "{$view_location}default/$view.php"; - - // try to include view - if (!file_exists($view_file) || !include($view_file)) { - // requested view does not exist - $error = "$viewtype/$view view does not exist."; - - // attempt to load default view - if ($viewtype != 'default') { - if (file_exists($default_view_file) && include($default_view_file)) { - // default view found - $error .= " Using default/$view instead."; - } else { - // no view found at all - $error = "Neither $viewtype/$view nor default/$view view exists."; - } - } - - // log warning - elgg_log($error, 'WARNING'); - } - } - - // Save the output buffer into the $content variable - $content = ob_get_clean(); - - // Plugin hook - $content = trigger_plugin_hook('display', 'view', - array('view' => $view_orig, 'vars' => $vars), $content); - - // Return $content - return $content; + $loaded_libraries[] = $name; } /** - * Returns whether the specified view exists + * Forward to $location. * - * @param string $view The view name - * @param string $viewtype If set, forces the viewtype - * @param bool $recurse If false, do not recursively check extensions - * @return true|false Depending on success + * Sends a 'Location: $location' header and exists. If headers have + * already been sent, returns FALSE. + * + * @param string $location URL to forward to browser to. Can be path relative to the network's URL. + * @param string $reason Short explanation for why we're forwarding + * + * @return false False if headers have been sent. Terminates execution if forwarding. + * @throws SecurityException */ -function elgg_view_exists($view, $viewtype = '', $recurse = true) { - global $CONFIG; - - // Detect view type - if (empty($viewtype)) { - $viewtype = elgg_get_viewtype(); - } - - if (!isset($CONFIG->views->locations[$viewtype][$view])) { - if (!isset($CONFIG->viewpath)) { - $location = dirname(dirname(dirname(__FILE__))) . "/views/"; - } else { - $location = $CONFIG->viewpath; +function forward($location = "", $reason = 'system') { + if (!headers_sent($file, $line)) { + if ($location === REFERER) { + $location = $_SERVER['HTTP_REFERER']; } - } else { - $location = $CONFIG->views->locations[$viewtype][$view]; - } - if (file_exists($location . "{$viewtype}/{$view}.php")) { - return true; - } + $location = elgg_normalize_url($location); - // If we got here then check whether this exists as an extension - // We optionally recursively check whether the extended view exists also for the viewtype - if ($recurse && isset($CONFIG->views->extensions[$view])) { - foreach( $CONFIG->views->extensions[$view] as $view_extension ) { - // do not recursively check to stay away from infinite loops - if (elgg_view_exists($view_extension, $viewtype, false)) { - return true; - } + // return new forward location or false to stop the forward or empty string to exit + $current_page = current_page_url(); + $params = array('current_url' => $current_page, 'forward_url' => $location); + $location = elgg_trigger_plugin_hook('forward', $reason, $params, $location); + + if ($location) { + header("Location: {$location}"); + exit; + } else if ($location === '') { + exit; } + } else { + throw new SecurityException(elgg_echo('SecurityException:ForwardFailedToRedirect', array($file, $line))); } - - return false; } /** - * Registers a view to be simply cached + * Register a JavaScript file for inclusion * - * Views cached in this manner must take no parameters and be login agnostic - - * that is to say, they look the same no matter who is logged in (or logged out). + * This function handles adding JavaScript to a web page. If multiple + * calls are made to register the same JavaScript file based on the $id + * variable, only the last file is included. This allows a plugin to add + * JavaScript from a view that may be called more than once. It also handles + * more than one plugin adding the same JavaScript. * - * CSS and the basic jS views are automatically cached like this. + * jQuery plugins often have filenames such as jquery.rating.js. A best practice + * is to base $name on the filename: "jquery.rating". It is recommended to not + * use version numbers in the name. * - * @param string $viewname View name - */ -function elgg_view_register_simplecache($viewname) { - global $CONFIG; - - if (!isset($CONFIG->views)) { - $CONFIG->views = new stdClass; - } - - if (!isset($CONFIG->views->simplecache)) { - $CONFIG->views->simplecache = array(); - } - - //if (elgg_view_exists($viewname)) - $CONFIG->views->simplecache[] = $viewname; -} - -/** - * Regenerates the simple cache. + * The JavaScript files can be local to the server or remote (such as + * Google's CDN). * - * @see elgg_view_register_simplecache + * @param string $name An identifier for the JavaScript library + * @param string $url URL of the JavaScript file + * @param string $location Page location: head or footer. (default: head) + * @param int $priority Priority of the JS file (lower numbers load earlier) * + * @return bool + * @since 1.8.0 */ -function elgg_view_regenerate_simplecache() { - global $CONFIG; - - // @todo elgg_view() checks if the page set is done (isset($CONFIG->pagesetupdone)) and - // triggers an event if it's not. Calling elgg_view() here breaks submenus - // (at least) because the page setup hook is called before any - // contexts can be correctly set (since this is called before page_handler()). - // To avoid this, lie about $CONFIG->pagehandlerdone to force - // the trigger correctly when the first view is actually being output. - $CONFIG->pagesetupdone = TRUE; - - if (isset($CONFIG->views->simplecache)) { - if (!file_exists($CONFIG->dataroot . 'views_simplecache')) { - @mkdir($CONFIG->dataroot . 'views_simplecache'); - } - - if (!empty($CONFIG->views->simplecache) && is_array($CONFIG->views->simplecache)) { - foreach($CONFIG->views->simplecache as $view) { - $viewcontents = elgg_view($view); - $viewname = md5(elgg_get_viewtype() . $view); - if ($handle = fopen($CONFIG->dataroot . 'views_simplecache/' . $viewname, 'w')) { - fwrite($handle, $viewcontents); - fclose($handle); - } - } - } - - datalist_set('simplecache_lastupdate', 0); - } - - unset($CONFIG->pagesetupdone); +function elgg_register_js($name, $url, $location = 'head', $priority = null) { + return elgg_register_external_file('js', $name, $url, $location, $priority); } /** - * Enables the simple cache. + * Unregister a JavaScript file * - * @see elgg_view_register_simplecache + * @param string $name The identifier for the JavaScript library * + * @return bool + * @since 1.8.0 */ - -function elgg_view_enable_simplecache() { - global $CONFIG; - if(!$CONFIG->simplecache_enabled) { - datalist_set('simplecache_enabled',1); - $CONFIG->simplecache_enabled = 1; - elgg_view_regenerate_simplecache(); - } +function elgg_unregister_js($name) { + return elgg_unregister_external_file('js', $name); } /** - * Disables the simple cache. + * Load a JavaScript resource on this page * - * @see elgg_view_register_simplecache + * This must be called before elgg_view_page(). It can be called before the + * script is registered. If you do not want a script loaded, unregister it. * - */ -function elgg_view_disable_simplecache() { - global $CONFIG; - if ($CONFIG->simplecache_enabled) { - datalist_set('simplecache_enabled',0); - $CONFIG->simplecache_enabled = 0; - - // purge simple cache - if ($handle = opendir($CONFIG->dataroot.'views_simplecache')) { - while (false !== ($file = readdir($handle))) { - if ($file != "." && $file != "..") { - unlink($CONFIG->dataroot.'views_simplecache/'.$file); - } - } - closedir($handle); - } - } -} - -/** - * This is a factory function which produces an ElggCache object suitable for caching file load paths. + * @param string $name Identifier of the JavaScript resource * - * TODO: Can this be done in a cleaner way? - * TODO: Swap to memcache etc? + * @return void + * @since 1.8.0 */ -function elgg_get_filepath_cache() { - global $CONFIG; - static $FILE_PATH_CACHE; - if (!$FILE_PATH_CACHE) $FILE_PATH_CACHE = new ElggFileCache($CONFIG->dataroot); - - return $FILE_PATH_CACHE; +function elgg_load_js($name) { + elgg_load_external_file('js', $name); } /** - * Function which resets the file path cache. + * Get the JavaScript URLs that are loaded * - */ -function elgg_filepath_cache_reset() { - $cache = elgg_get_filepath_cache(); - return $cache->delete('view_paths'); -} - -/** - * Saves a filepath cache. + * @param string $location 'head' or 'footer' * - * @param mixed $data + * @return array + * @since 1.8.0 */ -function elgg_filepath_cache_save($data) { - global $CONFIG; - - if ($CONFIG->viewpath_cache_enabled) { - $cache = elgg_get_filepath_cache(); - return $cache->save('view_paths', $data); - } - - return false; +function elgg_get_loaded_js($location = 'head') { + return elgg_get_loaded_external_files('js', $location); } /** - * Retrieve the contents of the filepath cache. + * Register a CSS file for inclusion in the HTML head * - */ -function elgg_filepath_cache_load() { - global $CONFIG; - - if ($CONFIG->viewpath_cache_enabled) { - $cache = elgg_get_filepath_cache(); - $cached_view_paths = $cache->load('view_paths'); - - if ($cached_view_paths) { - return $cached_view_paths; - } - } - - return NULL; -} - -/** - * Enable the filepath cache. + * @param string $name An identifier for the CSS file + * @param string $url URL of the CSS file + * @param int $priority Priority of the CSS file (lower numbers load earlier) * + * @return bool + * @since 1.8.0 */ -function elgg_enable_filepath_cache() { - global $CONFIG; - - datalist_set('viewpath_cache_enabled',1); - $CONFIG->viewpath_cache_enabled = 1; - elgg_filepath_cache_reset(); +function elgg_register_css($name, $url, $priority = null) { + return elgg_register_external_file('css', $name, $url, 'head', $priority); } /** - * Disable filepath cache. + * Unregister a CSS file * - */ -function elgg_disable_filepath_cache() { - global $CONFIG; - - datalist_set('viewpath_cache_enabled',0); - $CONFIG->viewpath_cache_enabled = 0; - elgg_filepath_cache_reset(); -} - -/** - * Internal function for retrieving views used by elgg_view_tree + * @param string $name The identifier for the CSS file * - * @param unknown_type $dir - * @param unknown_type $base - * @return unknown + * @return bool + * @since 1.8.0 */ -function elgg_get_views($dir, $base) { - $return = array(); - if (file_exists($dir) && is_dir($dir)) { - if ($handle = opendir($dir)) { - while ($view = readdir($handle)) { - if (!in_array($view, array('.','..','.svn','CVS'))) { - if (is_dir($dir . '/' . $view)) { - if ($val = elgg_get_views($dir . '/' . $view, $base . '/' . $view)) { - $return = array_merge($return, $val); - } - } else { - $view = str_replace('.php','',$view); - $return[] = $base . '/' . $view; - } - } - } - } - } - return $return; +function elgg_unregister_css($name) { + return elgg_unregister_external_file('css', $name); } /** - * @deprecated 1.7. Use elgg_extend_view(). - * @param $dir - * @param $base + * Load a CSS file for this page + * + * This must be called before elgg_view_page(). It can be called before the + * CSS file is registered. If you do not want a CSS file loaded, unregister it. + * + * @param string $name Identifier of the CSS file + * + * @return void + * @since 1.8.0 */ -function get_views($dir, $base) { - elgg_deprecated_notice('get_views() was deprecated by elgg_get_views()!', 1.7); - elgg_get_views($dir, $base); +function elgg_load_css($name) { + elgg_load_external_file('css', $name); } /** - * When given a partial view root (eg 'js' or 'page_elements'), returns an array of views underneath it + * Get the loaded CSS URLs * - * @param string $view_root The root view - * @param string $viewtype Optionally specify a view type other than the current one. - * @return array A list of view names underneath that root view + * @return array + * @since 1.8.0 */ -function elgg_view_tree($view_root, $viewtype = "") { - global $CONFIG; - static $treecache; - - // Get viewtype - if (!$viewtype) { - $viewtype = elgg_get_viewtype(); - } - - // Has the treecache been initialised? - if (!isset($treecache)) { - $treecache = array(); - } - // A little light internal caching - if (!empty($treecache[$view_root])) { - return $treecache[$view_root]; - } - - // Examine $CONFIG->views->locations - if (isset($CONFIG->views->locations[$viewtype])) { - foreach($CONFIG->views->locations[$viewtype] as $view => $path) { - $pos = strpos($view,$view_root); - if ($pos === 0) { - $treecache[$view_root][] = $view; - } - } - } - - // Now examine core - $location = $CONFIG->viewpath; - $viewtype = elgg_get_viewtype(); - $root = $location . $viewtype . '/' . $view_root; - - if (file_exists($root) && is_dir($root)) { - $val = elgg_get_views($root, $view_root); - if (!is_array($treecache[$view_root])) { - $treecache[$view_root] = array(); - } - $treecache[$view_root] = array_merge($treecache[$view_root], $val); - } - - return $treecache[$view_root]; +function elgg_get_loaded_css() { + return elgg_get_loaded_external_files('css', 'head'); } /** - * When given an entity, views it intelligently. + * Core registration function for external files * - * Expects a view to exist called entity-type/subtype, or for the entity to have a parameter - * 'view' which lists a different view to display. In both cases, elgg_view will be called with - * array('entity' => $entity, 'full' => $full) as its parameters, and therefore this is what - * the view should expect to receive. + * @param string $type Type of external resource (js or css) + * @param string $name Identifier used as key + * @param string $url URL + * @param string $location Location in the page to include the file + * @param int $priority Loading priority of the file * - * @param ElggEntity $entity The entity to display - * @param boolean $full Determines whether or not to display the full version of an object, or a smaller version for use in aggregators etc - * @param boolean $bypass If set to true, elgg_view will bypass any specified alternative template handler; by default, it will hand off to this if requested (see set_template_handler) - * @param boolean $debug If set to true, the viewer will complain if it can't find a view - * @return string HTML to display or false + * @return bool + * @since 1.8.0 */ -function elgg_view_entity(ElggEntity $entity, $full = false, $bypass = true, $debug = false) { - global $autofeed; - $autofeed = true; - - // No point continuing if entity is null - if (!$entity) { - return ''; - } +function elgg_register_external_file($type, $name, $url, $location, $priority = 500) { + global $CONFIG; - if (!($entity instanceof ElggEntity)) { + if (empty($name) || empty($url)) { return false; } - // if this entity has a view defined, use it - $view = $entity->view; - if (is_string($view)) { - return elgg_view($view, - array('entity' => $entity, 'full' => $full), - $bypass, - $debug); - } + $url = elgg_format_url($url); + $url = elgg_normalize_url($url); + + elgg_bootstrap_externals_data_structure($type); - $entity_type = $entity->getType(); + $name = trim(strtolower($name)); - $subtype = $entity->getSubtype(); - if (empty($subtype)) { - $subtype = $entity_type; + // normalize bogus priorities, but allow empty, null, and false to be defaults. + if (!is_numeric($priority)) { + $priority = 500; } - $contents = ''; - if (elgg_view_exists("{$entity_type}/{$subtype}")) { - $contents = elgg_view("{$entity_type}/{$subtype}", array( - 'entity' => $entity, - 'full' => $full - ), $bypass, $debug); - } - if (empty($contents)) { - $contents = elgg_view("{$entity_type}/default",array( - 'entity' => $entity, - 'full' => $full - ), $bypass, $debug); - } - // Marcus Povey 20090616 : Speculative and low impact approach for fixing #964 - if ($full) { - $annotations = elgg_view_entity_annotations($entity, $full); + // no negative priorities right now. + $priority = max((int)$priority, 0); - if ($annotations) { - $contents .= $annotations; - } - } - return $contents; -} + $item = elgg_extract($name, $CONFIG->externals_map[$type]); -/** - * When given an annotation, views it intelligently. - * - * This function expects annotation views to be of the form annotation/name, where name - * is the type of annotation. - * - * @param ElggAnnotation $annotation The annotation to display - * @param boolean $full Determines whether or not to display the full version of an object, or a smaller version for use in aggregators etc - * @param boolean $bypass If set to true, elgg_view will bypass any specified alternative template handler; by default, it will hand off to this if requested (see set_template_handler) - * @param boolean $debug If set to true, the viewer will complain if it can't find a view - * @return string HTML (etc) to display - */ -function elgg_view_annotation(ElggAnnotation $annotation, $bypass = true, $debug = false) { - global $autofeed; - $autofeed = true; - - $view = $annotation->view; - if (is_string($view)) { - return elgg_view($view,array('annotation' => $annotation), $bypass, $debug); - } + if ($item) { + // updating a registered item + // don't update loaded because it could already be set + $item->url = $url; + $item->location = $location; - $name = $annotation->name; - $intname = (int) $name; - if ("{$intname}" == "{$name}") { - $name = get_metastring($intname); - } - if (empty($name)) { - return ""; - } - - if (elgg_view_exists("annotation/{$name}")) { - return elgg_view("annotation/{$name}",array('annotation' => $annotation), $bypass, $debug); + // if loaded before registered, that means it hasn't been added to the list yet + if ($CONFIG->externals[$type]->contains($item)) { + $priority = $CONFIG->externals[$type]->move($item, $priority); + } else { + $priority = $CONFIG->externals[$type]->add($item, $priority); + } } else { - return elgg_view("annotation/default",array('annotation' => $annotation), $bypass, $debug); - } -} - - -/** - * Returns a view of a list of entities, plus navigation. It is intended that this function - * be called from other wrapper functions. - * - * @see list_entities - * @see list_user_objects - * @see list_user_friends_objects - * @see list_entities_from_metadata - * @see list_entities_from_metadata_multi - * @see list_entities_from_relationships - * @see list_site_members - * - * @param array $entities List of entities - * @param int $count The total number of entities across all pages - * @param int $offset The current indexing offset - * @param int $limit The number of entities to display per page - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow users to toggle to gallery view - * @param bool $pagination Whether pagination is offered. - * @return string The list of entities - */ -function elgg_view_entity_list($entities, $count, $offset, $limit, $fullview = true, $viewtypetoggle = true, $pagination = true) { - $count = (int) $count; - $offset = (int) $offset; - $limit = (int) $limit; - - $context = get_context(); - - $html = elgg_view('entities/entity_list',array( - 'entities' => $entities, - 'count' => $count, - 'offset' => $offset, - 'limit' => $limit, - 'baseurl' => $_SERVER['REQUEST_URI'], - 'fullview' => $fullview, - 'context' => $context, - 'viewtypetoggle' => $viewtypetoggle, - 'viewtype' => get_input('search_viewtype','list'), - 'pagination' => $pagination - )); - - return $html; -} + $item = new stdClass(); + $item->loaded = false; + $item->url = $url; + $item->location = $location; -/** - * Returns a view of a list of annotations, plus navigation. It is intended that this function - * be called from other wrapper functions. - * - * @param array $annotations List of annotations - * @param int $count The total number of annotations across all pages - * @param int $offset The current indexing offset - * @param int $limit The number of annotations to display per page - * @return string The list of annotations - */ -function elgg_view_annotation_list($annotations, $count, $offset, $limit) { - $count = (int) $count; - $offset = (int) $offset; - $limit = (int) $limit; - - $html = ""; - - $nav = elgg_view('navigation/pagination',array( - 'baseurl' => $_SERVER['REQUEST_URI'], - 'offset' => $offset, - 'count' => $count, - 'limit' => $limit, - 'word' => 'annoff', - 'nonefound' => false, - )); - - $html .= $nav; - - if (is_array($annotations) && sizeof($annotations) > 0) { - foreach($annotations as $annotation) { - $html .= elgg_view_annotation($annotation, "", false); - } + $priority = $CONFIG->externals[$type]->add($item, $priority); } - if ($count) { - $html .= $nav; - } + $CONFIG->externals_map[$type][$name] = $item; - return $html; + return $priority !== false; } /** - * Display a selective rendered list of annotations for a given entity. - * - * The list is produced as the result of the entity:annotate plugin hook - * and is designed to provide a more generic framework to allow plugins - * to extend the generic display of entities with their own annotation - * renderings. + * Unregister an external file * - * This is called automatically by the framework from elgg_view_entity() + * @param string $type Type of file: js or css + * @param string $name The identifier of the file * - * @param ElggEntity $entity - * @param bool $full - * @return string or false on failure + * @return bool + * @since 1.8.0 */ -function elgg_view_entity_annotations(ElggEntity $entity, $full = true) { - - // No point continuing if entity is null - if (!$entity) { - return false; - } - - if (!($entity instanceof ElggEntity)) { - return false; - } +function elgg_unregister_external_file($type, $name) { + global $CONFIG; - $entity_type = $entity->getType(); + elgg_bootstrap_externals_data_structure($type); - $annotations = trigger_plugin_hook('entity:annotate', $entity_type, - array( - 'entity' => $entity, - 'full' => $full, - ) - ); - - return $annotations; -} + $name = trim(strtolower($name)); + $item = elgg_extract($name, $CONFIG->externals_map[$type]); -/** - * Displays an internal layout for the use of a plugin canvas. - * Takes a variable number of parameters, which are made available - * in the views as $vars['area1'] .. $vars['areaN']. - * - * @param string $layout The name of the views in canvas/layouts/. - * @return string The layout - */ -function elgg_view_layout($layout) { - $arg = 1; - $param_array = array(); - while ($arg < func_num_args()) { - $param_array['area' . $arg] = func_get_arg($arg); - $arg++; + if ($item) { + unset($CONFIG->externals_map[$type][$name]); + return $CONFIG->externals[$type]->remove($item); } - if (elgg_view_exists("canvas/layouts/{$layout}")) { - return elgg_view("canvas/layouts/{$layout}",$param_array); - } else { - return elgg_view("canvas/default",$param_array); - } -} - -/** - * Returns a view for the page title - * - * @param string $title The page title - * @param string $submenu Should a submenu be displayed? (default false, use not recommended) - * @return string The HTML (etc) - */ -function elgg_view_title($title, $submenu = false) { - $title = elgg_view('page_elements/title', array('title' => $title, 'submenu' => $submenu)); - - return $title; + return false; } /** - * Adds an item to the submenu + * Load an external resource for use on this page * - * @param string $label The human-readable label - * @param string $link The URL of the submenu item - * @param boolean $onclick Used to provide a JS popup to confirm delete - * @param mixed $selected BOOL to force on/off, NULL to allow auto selection - */ -function add_submenu_item($label, $link, $group = 'a', $onclick = false, $selected = NULL) { - global $CONFIG; - - if (!isset($CONFIG->submenu)) { - $CONFIG->submenu = array(); - } - if (!isset($CONFIG->submenu[$group])) { - $CONFIG->submenu[$group] = array(); - } - - $item = new stdClass; - $item->value = $link; - $item->name = $label; - $item->onclick = $onclick; - $item->selected = $selected; - $CONFIG->submenu[$group][] = $item; -} - -/** - * Gets a formatted list of submenu items + * @param string $type Type of file: js or css + * @param string $name The identifier for the file * - * @params bool preselected Selected menu item - * @params bool preselectedgroup Selected menu item group - * @return string List of items + * @return void + * @since 1.8.0 */ -function get_submenu() { - $submenu_total = ""; +function elgg_load_external_file($type, $name) { global $CONFIG; - if (isset($CONFIG->submenu) && $submenu_register = $CONFIG->submenu) { - ksort($submenu_register); - $selected_key = NULL; - $selected_group = NULL; - - foreach($submenu_register as $groupname => $submenu_register_group) { - $submenu = ""; - - foreach($submenu_register_group as $key => $item) { - $selected = false; - // figure out the selected item if required - // if null, try to figure out what should be selected. - // warning: Fuzzy logic. - if (!$selected_key && !$selected_group) { - if ($item->selected === NULL) { - $uri_info = parse_url($_SERVER['REQUEST_URI']); - $item_info = parse_url($item->value); - - // don't want to mangle already encoded queries but want to - // make sure we're comparing encoded to encoded. - // for the record, queries *should* be encoded - $uri_params = array(); - $item_params = array(); - if (isset($uri_info['query'])) { - $uri_info['query'] = html_entity_decode($uri_info['query']); - $uri_params = elgg_parse_str($uri_info['query']); - } - if (isset($item_info['query'])) { - $item_info['query'] = html_entity_decode($item_info['query']); - $item_params = elgg_parse_str($item_info['query']); - } - - $uri_info['path'] = trim($uri_info['path'], '/'); - $item_info['path'] = trim($item_info['path'], '/'); - - // only if we're on the same path - // can't check server because sometimes it's not set in REQUEST_URI - if ($uri_info['path'] == $item_info['path']) { - - // if no query terms, we have a match - if (!isset($uri_info['query']) && !isset($item_info['query'])) { - $selected_key = $key; - $selected_group = $groupname; - $selected = TRUE; - } else { - if ($uri_info['query'] == $item_info['query']) { - //var_dump("Good on 1"); - $selected_key = $key; - $selected_group = $groupname; - $selected = TRUE; - } elseif (!count(array_diff($uri_params, $item_params))) { - $selected_key = $key; - $selected_group = $groupname; - $selected = TRUE; - } - } - } - // if TRUE or FALSE, set selected to this item. - // Group doesn't seem to have anything to do with selected? - } else { - $selected = $item->selected; - $selected_key = $key; - $selected_group = $groupname; - } - } + elgg_bootstrap_externals_data_structure($type); - $submenu .= elgg_view('canvas_header/submenu_template', array( - 'href' => $item->value, - 'label' => $item->name, - 'onclick' => $item->onclick, - 'selected' => $selected, - )); - - } + $name = trim(strtolower($name)); - $submenu_total .= elgg_view('canvas_header/submenu_group', array( - 'submenu' => $submenu, - 'group_name' => $groupname - )); + $item = elgg_extract($name, $CONFIG->externals_map[$type]); - } - } - - return $submenu_total; -} - - -/** - * Automatically views comments and a comment form relating to the given entity - * - * @param ElggEntity $entity The entity to comment on - * @return string|false The HTML (etc) for the comments, or false on failure - */ -function elgg_view_comments($entity){ - - if (!($entity instanceof ElggEntity)) { - return false; - } - - if ($comments = trigger_plugin_hook('comments',$entity->getType(),array('entity' => $entity),false)) { - return $comments; + if ($item) { + // update a registered item + $item->loaded = true; } else { - $comments = list_annotations($entity->getGUID(),'generic_comment'); + $item = new stdClass(); + $item->loaded = true; + $item->url = ''; + $item->location = ''; - //display the comment form - $comments .= elgg_view('comments/forms/edit',array('entity' => $entity)); - - return $comments; + $CONFIG->externals[$type]->add($item); + $CONFIG->externals_map[$type][$name] = $item; } } /** - * Count the number of comments attached to an entity + * Get external resource descriptors * - * @param ElggEntity $entity - * @return int Number of comments - */ -function elgg_count_comments($entity) { - if ($commentno = trigger_plugin_hook('comments:count', $entity->getType(), - array('entity' => $entity), false)) { - return $commentno; - } else { - return count_annotations($entity->getGUID(), "", "", "generic_comment"); - } -} - -/** - * Wrapper function to display search listings. + * @param string $type Type of file: js or css + * @param string $location Page location * - * @param string $icon The icon for the listing - * @param string $info Any information that needs to be displayed. - * @return string The HTML (etc) representing the listing - */ -function elgg_view_listing($icon, $info) { - return elgg_view('entities/entity_listing',array('icon' => $icon, 'info' => $info)); -} - -/** - * Sets an alternative function to handle templates, which will be passed to by elgg_view. - * This function must take the $view and $vars parameters from elgg_view: - * - * function my_template_function(string $view, array $vars = array()) - * - * @see elgg_view - * - * @param string $function_name The name of the function to pass to. - * @return true|false - */ -function set_template_handler($function_name) { - global $CONFIG; - if (!empty($function_name) && is_callable($function_name)) { - $CONFIG->template_handler = $function_name; - return true; - } - return false; -} - -/** - * Extends a view by adding other views to be displayed at the same time. - * - * @param string $view The view to add to. - * @param string $view_name The name of the view to extend - * @param int $priority The priority, from 0 to 1000, to add at (lowest numbers will be displayed first) - * @param string $viewtype Not used + * @return array + * @since 1.8.0 */ -function elgg_extend_view($view, $view_name, $priority = 501, $viewtype = '') { +function elgg_get_loaded_external_files($type, $location) { global $CONFIG; - if (!isset($CONFIG->views)) { - $CONFIG->views = new stdClass; - } - - if (!isset($CONFIG->views->extensions)) { - $CONFIG->views->extensions = array(); - } + if (isset($CONFIG->externals) && $CONFIG->externals[$type] instanceof ElggPriorityList) { + $items = $CONFIG->externals[$type]->getElements(); - if (!isset($CONFIG->views->extensions[$view])) { - $CONFIG->views->extensions[$view][500] = "{$view}"; - } - - while(isset($CONFIG->views->extensions[$view][$priority])) { - $priority++; + $callback = "return \$v->loaded == true && \$v->location == '$location';"; + $items = array_filter($items, create_function('$v', $callback)); + if ($items) { + array_walk($items, create_function('&$v,$k', '$v = $v->url;')); + } + return $items; } - - $CONFIG->views->extensions[$view][$priority] = "{$view_name}"; - ksort($CONFIG->views->extensions[$view]); + return array(); } /** - * @deprecated 1.7. Use elgg_extend_view(). - * @param $view - * @param $view_name - * @param $priority - * @param $viewtype - */ -function extend_view($view, $view_name, $priority = 501, $viewtype = '') { - elgg_deprecated_notice('extend_view() was deprecated by elgg_extend_view()!', 1.7); - elgg_extend_view($view, $view_name, $priority, $viewtype); -} - -/** - * Set an alternative base location for a view (as opposed to the default of $CONFIG->viewpath) + * Bootstraps the externals data structure in $CONFIG. * - * @param string $view The name of the view - * @param string $location The base location path + * @param string $type The type of external, js or css. + * @access private */ -function set_view_location($view, $location, $viewtype = '') { +function elgg_bootstrap_externals_data_structure($type) { global $CONFIG; - if (empty($viewtype)) { - $viewtype = 'default'; - } - - if (!isset($CONFIG->views)) { - $CONFIG->views = new stdClass; + if (!isset($CONFIG->externals)) { + $CONFIG->externals = array(); } - if (!isset($CONFIG->views->locations)) { - $CONFIG->views->locations = array($viewtype => array($view => $location)); - - } else if (!isset($CONFIG->views->locations[$viewtype])) { - $CONFIG->views->locations[$viewtype] = array($view => $location); - - } else { - $CONFIG->views->locations[$viewtype][$view] = $location; + if (!isset($CONFIG->externals[$type]) || !$CONFIG->externals[$type] instanceof ElggPriorityList) { + $CONFIG->externals[$type] = new ElggPriorityList(); } -} -/** - * Auto-registers views from a particular starting location - * - * @param string $view_base The base of the view name - * @param string $folder The folder to begin looking in - * @param string $base_location_path The base views directory to use with set_view_location - * @param string $viewtype The type of view we're looking at (default, rss, etc) - */ -function autoregister_views($view_base, $folder, $base_location_path, $viewtype) { - if (!isset($i)) { - $i = 0; + if (!isset($CONFIG->externals_map)) { + $CONFIG->externals_map = array(); } - if ($handle = opendir($folder)) { - while ($view = readdir($handle)) { - if (!in_array($view,array('.','..','.svn','CVS')) && !is_dir($folder . "/" . $view)) { - if ((substr_count($view,".php") > 0) || (substr_count($view,".png") > 0)) { - if (!empty($view_base)) { - $view_base_new = $view_base . "/"; - } else { - $view_base_new = ""; - } - - set_view_location($view_base_new . str_replace(".php","",$view), $base_location_path, $viewtype); - } - } else if (!in_array($view,array('.','..','.svn','CVS')) && is_dir($folder . "/" . $view)) { - if (!empty($view_base)) { - $view_base_new = $view_base . "/"; - } else { - $view_base_new = ""; - } - autoregister_views($view_base_new . $view, $folder . "/" . $view, $base_location_path, $viewtype); - } - } + if (!isset($CONFIG->externals_map[$type])) { + $CONFIG->externals_map[$type] = array(); } } /** - * Returns a representation of a full 'page' (which might be an HTML page, RSS file, etc, depending on the current view) + * Returns a list of files in $directory. * - * @param unknown_type $title - * @param unknown_type $body - * @return unknown - */ -function page_draw($title, $body, $sidebar = "") { - - // get messages - try for errors first - $sysmessages = system_messages(null, "errors"); - if (count($sysmessages["errors"]) == 0) { - // no errors so grab rest of messages - $sysmessages = system_messages(null, ""); - } else { - // we have errors - clear out remaining messages - system_messages(null, ""); - } - - // Draw the page - $output = elgg_view('pageshells/pageshell', array( - 'title' => $title, - 'body' => $body, - 'sidebar' => $sidebar, - 'sysmessages' => $sysmessages, - ) - ); - $split_output = str_split($output, 1024); - - foreach($split_output as $chunk) { - echo $chunk; - } -} - -/** - * Displays a UNIX timestamp in a friendly way (eg "less than a minute ago") + * Only returns files. Does not recurse into subdirs. * - * @param int $time A UNIX epoch timestamp - * @return string The friendly time - */ -function friendly_time($time) { - $diff = time() - ((int) $time); - - $minute = 60; - $hour = $minute * 60; - $day = $hour * 24; - - if ($diff < $minute) { - $friendly_time = elgg_echo("friendlytime:justnow"); - } else if ($diff < $hour) { - $diff = round($diff / $minute); - if ($diff == 0) { - $diff = 1; - } - - if ($diff > 1) { - $friendly_time = sprintf(elgg_echo("friendlytime:minutes"), $diff); - } else { - $friendly_time = sprintf(elgg_echo("friendlytime:minutes:singular"), $diff); - } - } else if ($diff < $day) { - $diff = round($diff / $hour); - if ($diff == 0) { - $diff = 1; - } - - if ($diff > 1) { - $friendly_time = sprintf(elgg_echo("friendlytime:hours"), $diff); - } else { - $friendly_time = sprintf(elgg_echo("friendlytime:hours:singular"), $diff); - } - } else { - $diff = round($diff / $day); - if ($diff == 0) { - $diff = 1; - } - - if ($diff > 1) { - $friendly_time = sprintf(elgg_echo("friendlytime:days"), $diff); - } else { - $friendly_time = sprintf(elgg_echo("friendlytime:days:singular"), $diff); - } - } - - $timestamp = htmlentities(date(elgg_echo('friendlytime:date_format'), $time)); - return "<acronym title=\"$timestamp\">$friendly_time</acronym>"; -} - -/** - * When given a title, returns a version suitable for inclusion in a URL + * @param string $directory Directory to look in + * @param array $exceptions Array of filenames to ignore + * @param array $list Array of files to append to + * @param mixed $extensions Array of extensions to allow, NULL for all. Use a dot: array('.php'). * - * @param string $title The title - * @return string The optimised title + * @return array Filenames in $directory, in the form $directory/filename. */ -function friendly_title($title) { - $title = trim($title); - $title = strtolower($title); - $title = preg_replace("/[^\w ]/","",$title); - $title = str_replace(" ","-",$title); - $title = str_replace("--","-",$title); - return $title; -} +function elgg_get_file_list($directory, $exceptions = array(), $list = array(), +$extensions = NULL) { -/** - * Library loading and handling - */ - -/** - * @deprecated 1.7 - */ -function get_library_files($directory, $exceptions = array(), $list = array()) { - elgg_deprecated_notice('get_library_files() deprecated by elgg_get_file_list()', 1.7); - return elgg_get_file_list($directory, $exceptions, $list, array('.php')); -} - -/** - * Returns a list of files in $directory - * - * @param str $directory - * @param array $exceptions Array of filenames to ignore - * @param array $list Array of files to append to - * @param mixed $extensions Array of extensions to allow, NULL for all. (With a dot: array('.php')) - * @return array - */ -function elgg_get_file_list($directory, $exceptions = array(), $list = array(), $extensions = NULL) { + $directory = sanitise_filepath($directory); if ($handle = opendir($directory)) { while (($file = readdir($handle)) !== FALSE) { - if (!is_file($file) || in_array($file, $exceptions)) { + if (!is_file($directory . $file) || in_array($file, $exceptions)) { continue; } if (is_array($extensions)) { if (in_array(strrchr($file, '.'), $extensions)) { - $list[] = $directory . "/" . $file; + $list[] = $directory . $file; } } else { - $list[] = $directory . "/" . $file; + $list[] = $directory . $file; } } + closedir($handle); } return $list; } /** - * Ensures that the installation has all the correct files, that PHP is configured correctly, and so on. - * Leaves appropriate messages in the error register if not. + * Sanitise file paths ensuring that they begin and end with slashes etc. * - * @return true|false True if everything is ok (or Elgg is fit enough to run); false if not. - */ -function sanitised() { - $sanitised = true; - - if (!file_exists(dirname(dirname(__FILE__)) . "/settings.php")) { - // See if we are being asked to save the file - $save_vars = get_input('db_install_vars'); - $result = ""; - if ($save_vars) { - $result = create_settings($save_vars, dirname(dirname(__FILE__)) . "/settings.example.php"); - - if (file_put_contents(dirname(dirname(__FILE__)) . "/settings.php", $result)) { - // blank result to stop it being displayed in textarea - $result = ""; - } - } - - // Recheck to see if the file is still missing - if (!file_exists(dirname(dirname(__FILE__)) . "/settings.php")) { - register_error(elgg_view("messages/sanitisation/settings", array('settings.php' => $result))); - $sanitised = false; - } - } - - if (!file_exists(dirname(dirname(dirname(__FILE__))) . "/.htaccess")) { - if (!@copy(dirname(dirname(dirname(__FILE__))) . "/htaccess_dist", dirname(dirname(dirname(__FILE__))) . "/.htaccess")) { - register_error(elgg_view("messages/sanitisation/htaccess", array('.htaccess' => file_get_contents(dirname(dirname(dirname(__FILE__))) . "/htaccess_dist")))); - $sanitised = false; - } - } - - return $sanitised; -} - -/** - * Registers - */ - -/** - * Adds an array with a name to a given generic array register. - * For example, these are used for menus. + * @param string $path The path + * @param bool $append_slash Add tailing slash * - * @param string $register_name The name of the top-level register - * @param string $subregister_name The name of the subregister - * @param mixed $subregister_value The value of the subregister - * @param array $children_array Optionally, an array of children - * @return true|false Depending on success + * @return string */ -function add_to_register($register_name, $subregister_name, $subregister_value, $children_array = array()) { - global $CONFIG; - - if (empty($register_name) || empty($subregister_name)) { - return false; - } - - if (!isset($CONFIG->registers)) { - $CONFIG->registers = array(); - } - - if (!isset($CONFIG->registers[$register_name])) { - $CONFIG->registers[$register_name] = array(); - } +function sanitise_filepath($path, $append_slash = TRUE) { + // Convert to correct UNIX paths + $path = str_replace('\\', '/', $path); + $path = str_replace('../', '/', $path); + // replace // with / except when preceeded by : + $path = preg_replace("/([^:])\/\//", "$1/", $path); - $subregister = new stdClass; - $subregister->name = $subregister_name; - $subregister->value = $subregister_value; + // Sort trailing slash + $path = trim($path); + // rtrim defaults plus / + $path = rtrim($path, " \n\t\0\x0B/"); - if (is_array($children_array)) { - $subregister->children = $children_array; + if ($append_slash) { + $path = $path . '/'; } - $CONFIG->registers[$register_name][$subregister_name] = $subregister; - return true; + return $path; } /** - * Returns a register object + * Queues a message to be displayed. * - * @param string $register_name The name of the register - * @param mixed $register_value The value of the register - * @param array $children_array Optionally, an array of children - * @return false|stdClass Depending on success - */ -function make_register_object($register_name, $register_value, $children_array = array()) { - elgg_deprecated_notice('make_register_object() is deprecated by add_submenu_item()', 1.7); - if (empty($register_name) || empty($register_value)) { - return false; - } - - $register = new stdClass; - $register->name = $register_name; - $register->value = $register_value; - $register->children = $children_array; - - return $register; -} - -/** - * If it exists, returns a particular register as an array + * Messages will not be displayed immediately, but are stored in + * for later display, usually upon next page load. * - * @param string $register_name The name of the register - * @return array|false Depending on success - */ -function get_register($register_name) { - global $CONFIG; - - if (isset($CONFIG->registers[$register_name])) { - return $CONFIG->registers[$register_name]; - } - - return false; -} - -/** - * Adds an item to the menu register - * This is used in the core to create the tools dropdown menu - * You can obtain the menu array by calling get_register('menu') + * The method of displaying these messages differs depending upon plugins and + * viewtypes. The core default viewtype retrieves messages in + * {@link views/default/page/shells/default.php} and displays messages as + * javascript popups. * - * @param string $menu_name The name of the menu item - * @param string $menu_url The URL of the page - * @param array $menu_children Optionally, an array of submenu items (not currently used) - * @param string $context (not used and will likely be deprecated) - * @return true|false Depending on success - */ -function add_menu($menu_name, $menu_url, $menu_children = array(), $context = "") { - global $CONFIG; - if (!isset($CONFIG->menucontexts)) { - $CONFIG->menucontexts = array(); - } - - if (empty($context)) { - $context = get_plugin_name(); - } - - $CONFIG->menucontexts[] = $context; - return add_to_register('menu', $menu_name, $menu_url, $menu_children); -} - -/** - * Returns a menu item for use in the children section of add_menu() - * This is not currently used in the Elgg core + * @internal Messages are stored as strings in the $_SESSION['msg'][$register] array. * - * @param string $menu_name The name of the menu item - * @param string $menu_url Its URL - * @return stdClass|false Depending on success - */ -function menu_item($menu_name, $menu_url) { - elgg_deprecated_notice('menu_item() is deprecated by add_submenu_item', 1.7); - return make_register_object($menu_name, $menu_url); -} - - -/** - * Message register handling - * If a null $message parameter is given, the function returns the array of messages so far and empties it - * based on the $register parameters. Otherwise, any message or array of messages is added. + * @warning This function is used to both add to and clear the message + * stack. If $messages is null, $register will be returned and cleared. + * If $messages is null and $register is empty, all messages will be + * returned and removed. * - * @param string|array $message Optionally, a single message or array of messages to add, (default: null) - * @param string $register This allows for different types of messages: "errors", "messages" (default: messages) - * @param bool $count Count the number of messages (default: false) - * @return true|false|array Either the array of messages, or a response regarding whether the message addition was successful + * @important This function handles the standard {@link system_message()} ($register = + * 'messages') as well as {@link register_error()} messages ($register = 'errors'). + * + * @param mixed $message Optionally, a single message or array of messages to add, (default: null) + * @param string $register Types of message: "error", "success" (default: success) + * @param bool $count Count the number of messages (default: false) + * + * @return bool|array Either the array of messages, or a response regarding + * whether the message addition was successful. + * @todo Clean up. Separate registering messages and retrieving them. */ - -function system_messages($message = null, $register = "messages", $count = false) { +function system_messages($message = null, $register = "success", $count = false) { if (!isset($_SESSION['msg'])) { $_SESSION['msg'] = array(); } @@ -1525,7 +570,7 @@ function system_messages($message = null, $register = "messages", $count = false return sizeof($_SESSION['msg'][$register]); } else { $count = 0; - foreach($_SESSION['msg'] as $register => $submessages) { + foreach ($_SESSION['msg'] as $submessages) { $count += sizeof($submessages); } return $count; @@ -1538,299 +583,414 @@ function system_messages($message = null, $register = "messages", $count = false * Counts the number of messages, either globally or in a particular register * * @param string $register Optionally, the register + * * @return integer The number of messages */ function count_messages($register = "") { - return system_messages(null,$register,true); + return system_messages(null, $register, true); } /** - * An alias for system_messages($message) to handle standard user information messages + * Display a system message on next page load. + * + * @see system_messages() * * @param string|array $message Message or messages to add - * @return true|false Success response + * + * @return bool */ function system_message($message) { - return system_messages($message, "messages"); + return system_messages($message, "success"); } /** - * An alias for system_messages($message) to handle error messages + * Display an error on next page load. + * + * @see system_messages() * - * @param string|array $message Error or errors to add - * @return true|false Success response + * @param string|array $error Error or errors to add + * + * @return bool */ function register_error($error) { - return system_messages($error, "errors"); + return system_messages($error, "error"); } /** - * Event register - * Adds functions to the register for a particular event, but also calls all functions registered to an event when required + * Register a callback as an Elgg event handler. + * + * Events are emitted by Elgg when certain actions occur. Plugins + * can respond to these events or halt them completely by registering a handler + * as a callback to an event. Multiple handlers can be registered for + * the same event and will be executed in order of $priority. Any handler + * returning false will halt the execution chain. * - * Event handler functions must be of the form: + * This function is called with the event name, event type, and handler callback name. + * Setting the optional $priority allows plugin authors to specify when the + * callback should be run. Priorities for plugins should be 1-1000. * - * event_handler_function($event, $object_type, $object); + * The callback is passed 3 arguments when called: $event, $type, and optional $params. * - * And must return true or false depending on success. A false will halt the event in its tracks and no more functions will be called. + * $event is the name of event being emitted. + * $type is the type of event or object concerned. + * $params is an optional parameter passed that can include a related object. See + * specific event documentation for details on which events pass what parameteres. * - * You can then simply register them using the following function. Optionally, this can be called with a priority nominally from 0 to 1000, where functions with lower priority values are called first (note that priorities CANNOT be negative): + * @tip If a priority isn't specified it is determined by the order the handler was + * registered relative to the event and type. For plugins, this generally means + * the earlier the plugin is in the load order, the earlier the priorities are for + * any event handlers. * - * register_elgg_event_handler($event, $object_type, $function_name [, $priority = 500]); + * @tip $event and $object_type can use the special keyword 'all'. Handler callbacks registered + * with $event = all will be called for all events of type $object_type. Similarly, + * callbacks registered with $object_type = all will be called for all events of type + * $event, regardless of $object_type. If $event and $object_type both are 'all', the + * handler callback will be called for all events. * - * Note that you can also use 'all' in place of both the event and object type. + * @tip Event handler callbacks are considered in the follow order: + * - Specific registration where 'all' isn't used. + * - Registration where 'all' is used for $event only. + * - Registration where 'all' is used for $type only. + * - Registration where 'all' is used for both. * - * To trigger an event properly, you should always use: + * @warning If you use the 'all' keyword, you must have logic in the handler callback to + * test the passed parameters before taking an action. * - * trigger_elgg_event($event, $object_type [, $object]); + * @tip When referring to events, the preferred syntax is "event, type". * - * Where $object is optional, and represents the $object_type the event concerns. This will return true if successful, or false if it fails. + * @internal Events are stored in $CONFIG->events as: + * <code> + * $CONFIG->events[$event][$type][$priority] = $callback; + * </code> + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $callback The handler callback + * @param int $priority The priority - 0 is default, negative before, positive after * - * @param string $event The type of event (eg 'init', 'update', 'delete') - * @param string $object_type The type of object (eg 'system', 'blog', 'user') - * @param string $function The name of the function that will handle the event - * @param int $priority A priority to add new event handlers at. Lower numbers will be called first (default 500) - * @param boolean $call Set to true to call the event rather than add to it (default false) - * @param mixed $object Optionally, the object the event is being performed on (eg a user) - * @return true|false Depending on success + * @return bool + * @link http://docs.elgg.org/Tutorials/Plugins/Events + * @example events/basic.php Basic example of registering an event handler callback. + * @example events/advanced.php Advanced example of registering an event handler + * callback and halting execution. + * @example events/all.php Example of how to use the 'all' keyword. */ -function events($event = "", $object_type = "", $function = "", $priority = 500, $call = false, $object = null) { +function elgg_register_event_handler($event, $object_type, $callback, $priority = 500) { global $CONFIG; + if (empty($event) || empty($object_type)) { + return false; + } + if (!isset($CONFIG->events)) { $CONFIG->events = array(); - } else if (!isset($CONFIG->events[$event]) && !empty($event)) { + } + if (!isset($CONFIG->events[$event])) { $CONFIG->events[$event] = array(); - } else if (!isset($CONFIG->events[$event][$object_type]) && !empty($event) && !empty($object_type)) { + } + if (!isset($CONFIG->events[$event][$object_type])) { $CONFIG->events[$event][$object_type] = array(); } - if (!$call) { - if (!empty($event) && !empty($object_type) && is_callable($function)) { - $priority = (int) $priority; - if ($priority < 0) { - $priority = 0; - } - while (isset($CONFIG->events[$event][$object_type][$priority])) { - $priority++; - } - $CONFIG->events[$event][$object_type][$priority] = $function; - ksort($CONFIG->events[$event][$object_type]); - return true; - } else { - return false; - } - } else { - $return = true; - if (!empty($CONFIG->events[$event][$object_type]) && is_array($CONFIG->events[$event][$object_type])) { - foreach($CONFIG->events[$event][$object_type] as $eventfunction) { - if ($eventfunction($event, $object_type, $object) === false) { - return false; - } - } - } - - if (!empty($CONFIG->events['all'][$object_type]) && is_array($CONFIG->events['all'][$object_type])) { - foreach($CONFIG->events['all'][$object_type] as $eventfunction) { - if ($eventfunction($event, $object_type, $object) === false) { - return false; - } - } - } - - if (!empty($CONFIG->events[$event]['all']) && is_array($CONFIG->events[$event]['all'])) { - foreach($CONFIG->events[$event]['all'] as $eventfunction) { - if ($eventfunction($event, $object_type, $object) === false) { - return false; - } - } - } - - if (!empty($CONFIG->events['all']['all']) && is_array($CONFIG->events['all']['all'])) { - foreach($CONFIG->events['all']['all'] as $eventfunction) { - if ($eventfunction($event, $object_type, $object) === false) { - return false; - } - } - } + if (!is_callable($callback, true)) { + return false; + } - return $return; + $priority = max((int) $priority, 0); + while (isset($CONFIG->events[$event][$object_type][$priority])) { + $priority++; } - - return false; + $CONFIG->events[$event][$object_type][$priority] = $callback; + ksort($CONFIG->events[$event][$object_type]); + return true; } /** - * Alias function for events, that registers a function to a particular kind of event + * Unregisters a callback for an event. * - * @param string $event The event type + * @param string $event The event type * @param string $object_type The object type - * @param string $function The function name - * @return true|false Depending on success - */ -function register_elgg_event_handler($event, $object_type, $function, $priority = 500) { - return events($event, $object_type, $function, $priority); -} - -/** - * Unregisters a function to a particular kind of event + * @param string $callback The callback * - * @param string $event The event type - * @param string $object_type The object type - * @param string $function The function name + * @return void + * @since 1.7 */ -function unregister_elgg_event_handler($event, $object_type, $function) { +function elgg_unregister_event_handler($event, $object_type, $callback) { global $CONFIG; - foreach($CONFIG->events[$event][$object_type] as $key => $event_function) { - if ($event_function == $function) { - unset($CONFIG->events[$event][$object_type][$key]); + + if (isset($CONFIG->events[$event]) && isset($CONFIG->events[$event][$object_type])) { + foreach ($CONFIG->events[$event][$object_type] as $key => $event_callback) { + if ($event_callback == $callback) { + unset($CONFIG->events[$event][$object_type][$key]); + } } } } /** - * Alias function for events, that triggers a particular kind of event + * Trigger an Elgg Event and run all handler callbacks registered to that event, type. * - * @param string $event The event type + * This function runs all handlers registered to $event, $object_type or + * the special keyword 'all' for either or both. + * + * $event is usually a verb: create, update, delete, annotation. + * + * $object_type is usually a noun: object, group, user, annotation, relationship, metadata. + * + * $object is usually an Elgg* object assciated with the event. + * + * @warning Elgg events should only be triggered by core. Plugin authors should use + * {@link trigger_elgg_plugin_hook()} instead. + * + * @tip When referring to events, the preferred syntax is "event, type". + * + * @internal Only rarely should events be changed, added, or removed in core. + * When making changes to events, be sure to first create a ticket on Github. + * + * @internal @tip Think of $object_type as the primary namespace element, and + * $event as the secondary namespace. + * + * @param string $event The event type * @param string $object_type The object type - * @param string $function The function name - * @return true|false Depending on success + * @param string $object The object involved in the event + * + * @return bool The result of running all handler callbacks. + * @link http://docs.elgg.org/Tutorials/Core/Events + * @internal @example events/emit.php Basic emitting of an Elgg event. */ -function trigger_elgg_event($event, $object_type, $object = null) { - $return = true; - $return1 = events($event, $object_type, "", null, true, $object); - if (!is_null($return1)) { - $return = $return1; +function elgg_trigger_event($event, $object_type, $object = null) { + global $CONFIG; + + $events = array(); + if (isset($CONFIG->events[$event][$object_type])) { + $events[] = $CONFIG->events[$event][$object_type]; } - return $return; + if (isset($CONFIG->events['all'][$object_type])) { + $events[] = $CONFIG->events['all'][$object_type]; + } + if (isset($CONFIG->events[$event]['all'])) { + $events[] = $CONFIG->events[$event]['all']; + } + if (isset($CONFIG->events['all']['all'])) { + $events[] = $CONFIG->events['all']['all']; + } + + $args = array($event, $object_type, $object); + + foreach ($events as $callback_list) { + if (is_array($callback_list)) { + foreach ($callback_list as $callback) { + if (is_callable($callback) && (call_user_func_array($callback, $args) === false)) { + return false; + } + } + } + } + + return true; } /** - * Register a function to a plugin hook for a particular entity type, with a given priority. + * Register a callback as a plugin hook handler. + * + * Plugin hooks allow developers to losely couple plugins and features by + * repsonding to and emitting {@link elgg_trigger_plugin_hook()} customizable hooks. + * Handler callbacks can respond to the hook, change the details of the hook, or + * ignore it. + * + * Multiple handlers can be registered for a plugin hook, and each callback + * is called in order of priority. If the return value of a handler is not + * null, that value is passed to the next callback in the call stack. When all + * callbacks have been run, the final value is passed back to the caller + * via {@link elgg_trigger_plugin_hook()}. * - * eg if you want the function "export_user" to be called when the hook "export" for "user" entities - * is run, use: + * Similar to Elgg Events, plugin hook handler callbacks are registered by passing + * a hook, a type, and a priority. * - * register_plugin_hook("export", "user", "export_user"); + * The callback is passed 4 arguments when called: $hook, $type, $value, and $params. * - * "all" is a valid value for both $hook and $entity_type. "none" is a valid value for $entity_type. + * - str $hook The name of the hook. + * - str $type The type of hook. + * - mixed $value The return value of the last handler or the default + * value if no other handlers have been called. + * - mixed $params An optional array of parameters. Used to provide additional + * information to plugins. * - * The export_user function would then be defined as: + * @internal Plugin hooks are stored in $CONFIG->hooks as: + * <code> + * $CONFIG->hooks[$hook][$type][$priority] = $callback; + * </code> * - * function export_user($hook, $entity_type, $returnvalue, $params); + * @tip Plugin hooks are similar to Elgg Events in that Elgg emits + * a plugin hook when certain actions occur, but a plugin hook allows you to alter the + * parameters, as well as halt execution. * - * Where $returnvalue is the return value returned by the last function returned by the hook, and - * $params is an array containing a set of parameters (or nothing). + * @tip If a priority isn't specified it is determined by the order the handler was + * registered relative to the event and type. For plugins, this generally means + * the earlier the plugin is in the load order, the earlier the priorities are for + * any event handlers. * - * @param string $hook The name of the hook - * @param string $entity_type The name of the type of entity (eg "user", "object" etc) - * @param string $function The name of a valid function to be run - * @param string $priority The priority - 0 is first, 1000 last, default is 500 - * @return true|false Depending on success + * @tip Like Elgg Events, $hook and $type can use the special keyword 'all'. + * Handler callbacks registered with $hook = all will be called for all hooks + * of type $type. Similarly, handlers registered with $type = all will be + * called for all hooks of type $event, regardless of $object_type. If $hook + * and $type both are 'all', the handler will be called for all hooks. + * + * @tip Plugin hooks are sometimes used to gather lists from plugins. This is + * usually done by pushing elements into an array passed in $params. Be sure + * to append to and then return $value so you don't overwrite other plugin's + * values. + * + * @warning Unlike Elgg Events, a handler that returns false will NOT halt the + * execution chain. + * + * @param string $hook The name of the hook + * @param string $type The type of the hook + * @param callable $callback The name of a valid function or an array with object and method + * @param int $priority The priority - 500 is default, lower numbers called first + * + * @return bool + * + * @example hooks/register/basic.php Registering for a plugin hook and examining the variables. + * @example hooks/register/advanced.php Registering for a plugin hook and changing the params. + * @link http://docs.elgg.org/Tutorials/Plugins/Hooks + * @since 1.8.0 */ -function register_plugin_hook($hook, $entity_type, $function, $priority = 500) { +function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority = 500) { global $CONFIG; + if (empty($hook) || empty($type)) { + return false; + } + if (!isset($CONFIG->hooks)) { $CONFIG->hooks = array(); - } else if (!isset($CONFIG->hooks[$hook]) && !empty($hook)) { + } + if (!isset($CONFIG->hooks[$hook])) { $CONFIG->hooks[$hook] = array(); - } else if (!isset($CONFIG->hooks[$hook][$entity_type]) && !empty($entity_type)) { - $CONFIG->hooks[$hook][$entity_type] = array(); + } + if (!isset($CONFIG->hooks[$hook][$type])) { + $CONFIG->hooks[$hook][$type] = array(); } - if (!empty($hook) && !empty($entity_type) && is_callable($function)) { - $priority = (int) $priority; - if ($priority < 0) { - $priority = 0; - } - while (isset($CONFIG->hooks[$hook][$entity_type][$priority])) { - $priority++; - } - $CONFIG->hooks[$hook][$entity_type][$priority] = $function; - ksort($CONFIG->hooks[$hook][$entity_type]); - return true; - } else { + if (!is_callable($callback, true)) { return false; } + + $priority = max((int) $priority, 0); + + while (isset($CONFIG->hooks[$hook][$type][$priority])) { + $priority++; + } + $CONFIG->hooks[$hook][$type][$priority] = $callback; + ksort($CONFIG->hooks[$hook][$type]); + return true; } /** - * Unregister a function to a plugin hook for a particular entity type + * Unregister a callback as a plugin hook. * - * @param string $hook The name of the hook - * @param string $entity_type The name of the type of entity (eg "user", "object" etc) - * @param string $function The name of a valid function to be run + * @param string $hook The name of the hook + * @param string $entity_type The name of the type of entity (eg "user", "object" etc) + * @param callable $callback The PHP callback to be removed + * + * @return void + * @since 1.8.0 */ -function unregister_plugin_hook($hook, $entity_type, $function) { +function elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback) { global $CONFIG; - foreach($CONFIG->hooks[$hook][$entity_type] as $key => $hook_function) { - if ($hook_function == $function) { - unset($CONFIG->hooks[$hook][$entity_type][$key]); + + if (isset($CONFIG->hooks[$hook]) && isset($CONFIG->hooks[$hook][$entity_type])) { + foreach ($CONFIG->hooks[$hook][$entity_type] as $key => $hook_callback) { + if ($hook_callback == $callback) { + unset($CONFIG->hooks[$hook][$entity_type][$key]); + } } } } /** - * Triggers a plugin hook, with various parameters as an array. For example, to provide - * a 'foo' hook that concerns an entity of type 'bar', with a parameter called 'param1' - * with value 'value1', that by default returns true, you'd call: + * Trigger a Plugin Hook and run all handler callbacks registered to that hook:type. + * + * This function runs all handlers regsitered to $hook, $type or + * the special keyword 'all' for either or both. + * + * Use $params to send additional information to the handler callbacks. * - * trigger_plugin_hook('foo', 'bar', array('param1' => 'value1'), true); + * $returnvalue Is the initial value to pass to the handlers, which can + * then change it. It is useful to use $returnvalue to set defaults. + * If no handlers are registered, $returnvalue is immediately returned. * - * @see register_plugin_hook - * @param string $hook The name of the hook to trigger - * @param string $entity_type The name of the entity type to trigger it for (or "all", or "none") - * @param array $params Any parameters. It's good practice to name the keys, i.e. by using array('name' => 'value', 'name2' => 'value2') - * @param mixed $returnvalue An initial return value - * @return mixed|null The cumulative return value for the plugin hook functions + * $hook is usually a verb: import, get_views, output. + * + * $type is usually a noun: user, ecml, page. + * + * @tip Like Elgg Events, $hook and $type can use the special keyword 'all'. + * Handler callbacks registered with $hook = all will be called for all hooks + * of type $type. Similarly, handlers registered with $type = all will be + * called for all hooks of type $event, regardless of $object_type. If $hook + * and $type both are 'all', the handler will be called for all hooks. + * + * @internal The checks for $hook and/or $type not being equal to 'all' is to + * prevent a plugin hook being registered with an 'all' being called more than + * once if the trigger occurs with an 'all'. An example in core of this is in + * actions.php: + * elgg_trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', ...) + * + * @see elgg_register_plugin_hook_handler() + * + * @param string $hook The name of the hook to trigger ("all" will + * trigger for all $types regardless of $hook value) + * @param string $type The type of the hook to trigger ("all" will + * trigger for all $hooks regardless of $type value) + * @param mixed $params Additional parameters to pass to the handlers + * @param mixed $returnvalue An initial return value + * + * @return mixed|null The return value of the last handler callback called + * + * @example hooks/trigger/basic.php Trigger a hook that determins if execution + * should continue. + * @example hooks/trigger/advanced.php Trigger a hook with a default value and use + * the results to populate a menu. + * @example hooks/basic.php Trigger and respond to a basic plugin hook. + * @link http://docs.elgg.org/Tutorials/Plugins/Hooks + * + * @since 1.8.0 */ -function trigger_plugin_hook($hook, $entity_type, $params = null, $returnvalue = null) { +function elgg_trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) { global $CONFIG; - //if (!isset($CONFIG->hooks) || !isset($CONFIG->hooks[$hook]) || !isset($CONFIG->hooks[$hook][$entity_type])) - // return $returnvalue; - - if (!empty($CONFIG->hooks[$hook][$entity_type]) && is_array($CONFIG->hooks[$hook][$entity_type])) { - foreach($CONFIG->hooks[$hook][$entity_type] as $hookfunction) { - $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); - if (!is_null($temp_return_value)) { - $returnvalue = $temp_return_value; - } + $hooks = array(); + if (isset($CONFIG->hooks[$hook][$type])) { + if ($hook != 'all' && $type != 'all') { + $hooks[] = $CONFIG->hooks[$hook][$type]; } } - //else - //if (!isset($CONFIG->hooks['all'][$entity_type])) - // return $returnvalue; - - if (!empty($CONFIG->hooks['all'][$entity_type]) && is_array($CONFIG->hooks['all'][$entity_type])) { - foreach($CONFIG->hooks['all'][$entity_type] as $hookfunction) { - $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); - if (!is_null($temp_return_value)) $returnvalue = $temp_return_value; + if (isset($CONFIG->hooks['all'][$type])) { + if ($type != 'all') { + $hooks[] = $CONFIG->hooks['all'][$type]; } } - //else - //if (!isset($CONFIG->hooks[$hook]['all'])) - // return $returnvalue; - - if (!empty($CONFIG->hooks[$hook]['all']) && is_array($CONFIG->hooks[$hook]['all'])) { - foreach($CONFIG->hooks[$hook]['all'] as $hookfunction) { - $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); - if (!is_null($temp_return_value)) { - $returnvalue = $temp_return_value; - } + if (isset($CONFIG->hooks[$hook]['all'])) { + if ($hook != 'all') { + $hooks[] = $CONFIG->hooks[$hook]['all']; } } - //else - //if (!isset($CONFIG->hooks['all']['all'])) - // return $returnvalue; + if (isset($CONFIG->hooks['all']['all'])) { + $hooks[] = $CONFIG->hooks['all']['all']; + } - if (!empty($CONFIG->hooks['all']['all']) && is_array($CONFIG->hooks['all']['all'])) { - foreach($CONFIG->hooks['all']['all'] as $hookfunction) { - $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); - if (!is_null($temp_return_value)) { - $returnvalue = $temp_return_value; + foreach ($hooks as $callback_list) { + if (is_array($callback_list)) { + foreach ($callback_list as $hookcallback) { + if (is_callable($hookcallback)) { + $args = array($hook, $type, $returnvalue, $params); + $temp_return_value = call_user_func_array($hookcallback, $args); + if (!is_null($temp_return_value)) { + $returnvalue = $temp_return_value; + } + } } } } @@ -1839,26 +999,85 @@ function trigger_plugin_hook($hook, $entity_type, $params = null, $returnvalue = } /** - * Error handling + * Intercepts, logs, and displays uncaught exceptions. + * + * @warning This function should never be called directly. + * + * @see http://www.php.net/set-exception-handler + * + * @param Exception $exception The exception being handled + * + * @return void + * @access private */ +function _elgg_php_exception_handler($exception) { + $timestamp = time(); + error_log("Exception #$timestamp: $exception"); + + // Wipe any existing output buffer + ob_end_clean(); + + // make sure the error isn't cached + header("Cache-Control: no-cache, must-revalidate", true); + header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true); + // @note Do not send a 500 header because it is not a server error + + try { + // we don't want the 'pagesetup', 'system' event to fire + global $CONFIG; + $CONFIG->pagesetupdone = true; + + elgg_set_viewtype('failsafe'); + if (elgg_is_admin_logged_in()) { + $body = elgg_view("messages/exceptions/admin_exception", array( + 'object' => $exception, + 'ts' => $timestamp + )); + } else { + $body = elgg_view("messages/exceptions/exception", array( + 'object' => $exception, + 'ts' => $timestamp + )); + } + echo elgg_view_page(elgg_echo('exception:title'), $body); + } catch (Exception $e) { + $timestamp = time(); + $message = $e->getMessage(); + echo "Fatal error in exception handler. Check log for Exception #$timestamp"; + error_log("Exception #$timestamp : fatal error in exception handler : $message"); + } +} /** - * PHP Error handler function. - * This function acts as a wrapper to catch and report PHP error messages. + * Intercepts catchable PHP errors. + * + * @warning This function should never be called directly. + * + * @internal + * For catchable fatal errors, throws an Exception with the error. + * + * For non-fatal errors, depending upon the debug settings, either + * log the error or ignore it. * * @see http://www.php.net/set-error-handler - * @param int $errno The level of the error raised - * @param string $errmsg The error message + * + * @param int $errno The level of the error raised + * @param string $errmsg The error message * @param string $filename The filename the error was raised in - * @param int $linenum The line number the error was raised at - * @param array $vars An array that points to the active symbol table at the point that the error occurred + * @param int $linenum The line number the error was raised at + * @param array $vars An array that points to the active symbol table where error occurred + * + * @return true + * @throws Exception + * @access private + * @todo Replace error_log calls with elgg_log calls. */ -function __elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { +function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { $error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)"; switch ($errno) { case E_USER_ERROR: - error_log("ERROR: $error"); + error_log("PHP ERROR: $error"); register_error("ERROR: $error"); // Since this is a fatal error, we want to stop any further execution but do so gracefully. @@ -1867,13 +1086,18 @@ function __elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { case E_WARNING : case E_USER_WARNING : - error_log("WARNING: $error"); + case E_RECOVERABLE_ERROR: // (e.g. type hint violation) + + // check if the error wasn't suppressed by the error control operator (@) + if (error_reporting()) { + error_log("PHP WARNING: $error"); + } break; default: global $CONFIG; if (isset($CONFIG->debug) && $CONFIG->debug === 'NOTICE') { - error_log("NOTICE: $error"); + error_log("PHP NOTICE: $error"); } } @@ -1881,19 +1105,25 @@ function __elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { } /** - * Throws a message to the Elgg logger + * Display or log a message. + * + * If $level is >= to the debug setting in {@link $CONFIG->debug}, the + * message will be sent to {@link elgg_dump()}. Messages with lower + * priority than {@link $CONFIG->debug} are ignored. * - * The Elgg log is currently implemented such that any messages sent at a level - * greater than or equal to the debug setting will be sent to elgg_dump. - * The default location for elgg_dump is the screen except for notices. + * {@link elgg_dump()} outputs all levels but NOTICE to screen by default. * - * Note: No messages will be displayed unless debugging has been enabled. + * @note No messages will be displayed unless debugging has been enabled. + * + * @param string $message User message + * @param string $level NOTICE | WARNING | ERROR | DEBUG * - * @param str $message User message - * @param str $level NOTICE | WARNING | ERROR | DEBUG * @return bool + * @since 1.7.0 + * @todo This is complicated and confusing. Using int constants for debug levels will + * make things easier. */ -function elgg_log($message, $level='NOTICE') { +function elgg_log($message, $level = 'NOTICE') { global $CONFIG; // only log when debugging is enabled @@ -1929,27 +1159,46 @@ function elgg_log($message, $level='NOTICE') { } /** - * Extremely generic var_dump-esque wrapper + * Logs or displays $value. + * + * If $to_screen is true, $value is displayed to screen. Else, + * it is handled by PHP's {@link error_log()} function. * - * Immediately dumps the given $value as a human-readable string. - * The $value can instead be written to the screen or server log depending on - * the value of the $to_screen flag. + * A {@elgg_plugin_hook debug log} is called. If a handler returns + * false, it will stop the default logging method. + * + * @param mixed $value The value + * @param bool $to_screen Display to screen? + * @param string $level The debug level * - * @param mixed $value - * @param bool $to_screen - * @param string $level * @return void + * @since 1.7.0 */ function elgg_dump($value, $to_screen = TRUE, $level = 'NOTICE') { + global $CONFIG; // plugin can return false to stop the default logging method - $params = array('level' => $level, - 'msg' => $value, - 'to_screen' => $to_screen); - if (!trigger_plugin_hook('debug', 'log', $params, true)) { + $params = array( + 'level' => $level, + 'msg' => $value, + 'to_screen' => $to_screen, + ); + if (!elgg_trigger_plugin_hook('debug', 'log', $params, true)) { return; } + // Do not want to write to screen before page creation has started. + // This is not fool-proof but probably fixes 95% of the cases when logging + // results in data sent to the browser before the page is begun. + if (!isset($CONFIG->pagesetupdone)) { + $to_screen = FALSE; + } + + // Do not want to write to JS or CSS pages + if (elgg_in_context('js') || elgg_in_context('css')) { + $to_screen = FALSE; + } + if ($to_screen == TRUE) { echo '<pre>'; print_r($value); @@ -1960,406 +1209,544 @@ function elgg_dump($value, $to_screen = TRUE, $level = 'NOTICE') { } /** - * Custom exception handler. - * This function catches any thrown exceptions and handles them appropriately. + * Sends a notice about deprecated use of a function, view, etc. * - * @see http://www.php.net/set-exception-handler - * @param Exception $exception The exception being handled + * This function either displays or logs the deprecation message, + * depending upon the deprecation policies in {@link CODING.txt}. + * Logged messages are sent with the level of 'WARNING'. Only admins + * get visual deprecation notices. When non-admins are logged in, the + * notices are sent to PHP's log through elgg_dump(). + * + * A user-visual message will be displayed if $dep_version is greater + * than 1 minor releases lower than the current Elgg version, or at all + * lower than the current Elgg major version. + * + * @note This will always at least log a warning. Don't use to pre-deprecate things. + * This assumes we are releasing in order and deprecating according to policy. + * + * @see CODING.txt + * + * @param string $msg Message to log / display. + * @param string $dep_version Human-readable *release* version: 1.7, 1.8, ... + * @param int $backtrace_level How many levels back to display the backtrace. + * Useful if calling from functions that are called + * from other places (like elgg_view()). Set to -1 + * for a full backtrace. + * + * @return bool + * @since 1.7.0 */ -function __elgg_php_exception_handler($exception) { - error_log("*** FATAL EXCEPTION *** : " . $exception); - - ob_end_clean(); // Wipe any existing output buffer - - // make sure the error isn't cached - header("Cache-Control: no-cache, must-revalidate", true); - header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true); - //header("Internal Server Error", true, 500); +function elgg_deprecated_notice($msg, $dep_version, $backtrace_level = 1) { + // if it's a major release behind, visual and logged + // if it's a 1 minor release behind, visual and logged + // if it's for current minor release, logged. + // bugfixes don't matter because we are not deprecating between them - $body = elgg_view("messages/exceptions/exception",array('object' => $exception)); - page_draw(elgg_echo('exception:title'), $body); -} + if (!$dep_version) { + return false; + } -/** - * Data lists - */ + $elgg_version = get_version(true); + $elgg_version_arr = explode('.', $elgg_version); + $elgg_major_version = (int)$elgg_version_arr[0]; + $elgg_minor_version = (int)$elgg_version_arr[1]; -$DATALIST_CACHE = array(); + $dep_major_version = (int)$dep_version; + $dep_minor_version = 10 * ($dep_version - $dep_major_version); -/** - * Get the value of a particular piece of data in the datalist - * - * @param string $name The name of the datalist - * @return string|false Depending on success - */ -function datalist_get($name) { - global $CONFIG, $DATALIST_CACHE; + $visual = false; - // We need this, because sometimes datalists are received before the database is created - if (!is_db_installed()) { - return false; + if (($dep_major_version < $elgg_major_version) || + ($dep_minor_version < $elgg_minor_version)) { + $visual = true; } - $name = sanitise_string($name); - if (isset($DATALIST_CACHE[$name])) { - return $DATALIST_CACHE[$name]; - } + $msg = "Deprecated in $dep_major_version.$dep_minor_version: $msg"; - // If memcache enabled then cache value in memcache - $value = null; - static $datalist_memcache; - if ((!$datalist_memcache) && (is_memcache_available())) { - $datalist_memcache = new ElggMemcache('datalist_memcache'); - } - if ($datalist_memcache) { - $value = $datalist_memcache->load($name); - } - if ($value) { - return $value; + if ($visual && elgg_is_admin_logged_in()) { + register_error($msg); } - // [Marcus Povey 20090217 : Now retrieving all datalist values on first load as this saves about 9 queries per page] - $result = get_data("SELECT * from {$CONFIG->dbprefix}datalists"); - if ($result) { - foreach ($result as $row) { - $DATALIST_CACHE[$row->name] = $row->value; + // Get a file and line number for the log. Never show this in the UI. + // Skip over the function that sent this notice and see who called the deprecated + // function itself. + $msg .= " Called from "; + $stack = array(); + $backtrace = debug_backtrace(); + // never show this call. + array_shift($backtrace); + $i = count($backtrace); - // Cache it if memcache is available - if ($datalist_memcache) { - $datalist_memcache->save($row->name, $row->value); - } - } + foreach ($backtrace as $trace) { + $stack[] = "[#$i] {$trace['file']}:{$trace['line']}"; + $i--; - if (isset($DATALIST_CACHE[$name])) { - return $DATALIST_CACHE[$name]; + if ($backtrace_level > 0) { + if ($backtrace_level <= 1) { + break; + } + $backtrace_level--; } } + $msg .= implode("<br /> -> ", $stack); - /*if ($row = get_data_row("SELECT value from {$CONFIG->dbprefix}datalists where name = '{$name}' limit 1")) { - $DATALIST_CACHE[$name] = $row->value; - - // Cache it if memcache is available - if ($datalist_memcache) $datalist_memcache->save($name, $row->value); - - return $row->value; - }*/ + elgg_log($msg, 'WARNING'); - return false; + return true; } /** - * Sets the value for a system-wide piece of data (overwriting a previous value if it exists) + * Returns the current page's complete URL. * - * @param string $name The name of the datalist - * @param string $value The new value - * @return true + * The current URL is assembled using the network's wwwroot and the request URI + * in $_SERVER as populated by the web server. This function will include + * any schemes, usernames and passwords, and ports. + * + * @return string The current page URL. */ -function datalist_set($name, $value) { - - global $CONFIG, $DATALIST_CACHE; +function current_page_url() { + $url = parse_url(elgg_get_site_url()); - $name = sanitise_string($name); - $value = sanitise_string($value); + $page = $url['scheme'] . "://"; - // If memcache is available then invalidate the cached copy - static $datalist_memcache; - if ((!$datalist_memcache) && (is_memcache_available())) { - $datalist_memcache = new ElggMemcache('datalist_memcache'); + // user/pass + if ((isset($url['user'])) && ($url['user'])) { + $page .= $url['user']; + } + if ((isset($url['pass'])) && ($url['pass'])) { + $page .= ":" . $url['pass']; + } + if ((isset($url['user']) && $url['user']) || + (isset($url['pass']) && $url['pass'])) { + $page .= "@"; } - if ($datalist_memcache) { - $datalist_memcache->delete($name); + $page .= $url['host']; + + if ((isset($url['port'])) && ($url['port'])) { + $page .= ":" . $url['port']; } - //delete_data("delete from {$CONFIG->dbprefix}datalists where name = '{$name}'"); - insert_data("INSERT into {$CONFIG->dbprefix}datalists set name = '{$name}', value = '{$value}' ON DUPLICATE KEY UPDATE value='{$value}'"); + $page = trim($page, "/"); - $DATALIST_CACHE[$name] = $value; + $page .= $_SERVER['REQUEST_URI']; - return true; + return $page; +} + +/** + * Return the full URL of the current page. + * + * @return string The URL + * @todo Combine / replace with current_page_url() + */ +function full_url() { + $s = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : ""; + $protocol = substr(strtolower($_SERVER["SERVER_PROTOCOL"]), 0, + strpos(strtolower($_SERVER["SERVER_PROTOCOL"]), "/")) . $s; + + $port = ($_SERVER["SERVER_PORT"] == "80" || $_SERVER["SERVER_PORT"] == "443") ? + "" : (":" . $_SERVER["SERVER_PORT"]); + + // This is here to prevent XSS in poorly written browsers used by 80% of the population. + // https://github.com/Elgg/Elgg/commit/0c947e80f512cb0a482b1864fd0a6965c8a0cd4a + $quotes = array('\'', '"'); + $encoded = array('%27', '%22'); + + return $protocol . "://" . $_SERVER['SERVER_NAME'] . $port . + str_replace($quotes, $encoded, $_SERVER['REQUEST_URI']); } /** - * Runs a function once - not per page load, but per installation. - * If you like, you can also set the threshold for the function execution - i.e., - * if the function was executed before or on $timelastupdatedcheck, this - * function will run it again. + * Builds a URL from the a parts array like one returned by {@link parse_url()}. + * + * @note If only partial information is passed, a partial URL will be returned. + * + * @param array $parts Associative array of URL components like parse_url() returns + * @param bool $html_encode HTML Encode the url? * - * @param string $functionname The name of the function you want to run. - * @param int $timelastupdatedcheck Optionally, the UNIX epoch timestamp of the execution threshold - * @return true|false Depending on success. + * @return string Full URL + * @since 1.7.0 */ -function run_function_once($functionname, $timelastupdatedcheck = 0) { - if ($lastupdated = datalist_get($functionname)) { - $lastupdated = (int) $lastupdated; - } else { - $lastupdated = 0; - } - if (is_callable($functionname) && $lastupdated <= $timelastupdatedcheck) { - $functionname(); - datalist_set($functionname,time()); - return true; +function elgg_http_build_url(array $parts, $html_encode = TRUE) { + // build only what's given to us. + $scheme = isset($parts['scheme']) ? "{$parts['scheme']}://" : ''; + $host = isset($parts['host']) ? "{$parts['host']}" : ''; + $port = isset($parts['port']) ? ":{$parts['port']}" : ''; + $path = isset($parts['path']) ? "{$parts['path']}" : ''; + $query = isset($parts['query']) ? "?{$parts['query']}" : ''; + + $string = $scheme . $host . $port . $path . $query; + + if ($html_encode) { + return elgg_format_url($string); } else { - return false; + return $string; } } /** - * Sends a notice about deprecated use of a function, view, etc. - * Note: This will ALWAYS at least log a warning. Don't use to pre-deprecate things. - * This assumes we are releasing in order and deprecating according to policy. + * Adds action tokens to URL * - * @param str $msg Message to log / display. - * @param str $version human-readable *release* version the function was deprecated. No bloody A, B, (R)C, or D. + * As of 1.7.0 action tokens are required on all actions. + * Use this function to append action tokens to a URL's GET parameters. + * This will preserve any existing GET parameters. * - * @return bool + * @note If you are using {@elgg_view input/form} you don't need to + * add tokens to the action. The form view automatically handles + * tokens. + * + * @param string $url Full action URL + * @param bool $html_encode HTML encode the url? (default: false) + * + * @return string URL with action tokens + * @since 1.7.0 + * @link http://docs.elgg.org/Tutorials/Actions */ -function elgg_deprecated_notice($msg, $dep_version) { - // if it's a major release behind, visual and logged - // if it's a 2 minor releases behind, visual and logged - // if it's 1 minor release behind, logged. - // bugfixes don't matter because you're not deprecating between them, RIGHT? +function elgg_add_action_tokens_to_url($url, $html_encode = FALSE) { + $components = parse_url(elgg_normalize_url($url)); - if (!$dep_version) { - return FALSE; + if (isset($components['query'])) { + $query = elgg_parse_str($components['query']); + } else { + $query = array(); } - $elgg_version = get_version(TRUE); - $elgg_version_arr = explode('.', $elgg_version); - $elgg_major_version = $elgg_version_arr[0]; - $elgg_minor_version = $elgg_version_arr[1]; + if (isset($query['__elgg_ts']) && isset($query['__elgg_token'])) { + return $url; + } - $dep_version_arr = explode('.', $dep_version); - $dep_major_version = $dep_version_arr[0]; - $dep_minor_version = $dep_version_arr[1]; + // append action tokens to the existing query + $query['__elgg_ts'] = time(); + $query['__elgg_token'] = generate_action_token($query['__elgg_ts']); + $components['query'] = http_build_query($query); - $last_working_version = $dep_minor_version - 1; + // rebuild the full url + return elgg_http_build_url($components, $html_encode); +} - $visual = FALSE; +/** + * Removes an element from a URL's query string. + * + * @note You can send a partial URL string. + * + * @param string $url Full URL + * @param string $element The element to remove + * + * @return string The new URL with the query element removed. + * @since 1.7.0 + */ +function elgg_http_remove_url_query_element($url, $element) { + $url_array = parse_url($url); - // use version_compare to account for 1.7a < 1.7 - if (($dep_major_version < $elgg_major_version) - || (($elgg_minor_version - $last_working_version) > 1)) { - $visual = TRUE; + if (isset($url_array['query'])) { + $query = elgg_parse_str($url_array['query']); + } else { + // nothing to remove. Return original URL. + return $url; } - $msg = "Deprecated in $dep_version: $msg"; - - if ($visual) { - register_error($msg); + if (array_key_exists($element, $query)) { + unset($query[$element]); } - // Get a file and line number for the log. Never show this in the UI. - // Skip over the function that sent this notice and see who called the deprecated - // function itself. - $backtrace = debug_backtrace(); - $caller = $backtrace[1]; - $msg .= " (Called from {$caller['file']}:{$caller['line']})"; - - elgg_log($msg, 'WARNING'); - - return TRUE; + $url_array['query'] = http_build_query($query); + $string = elgg_http_build_url($url_array, false); + return $string; } - /** - * Privilege elevation and gatekeeper code + * Adds an element or elements to a URL's query string. + * + * @param string $url The URL + * @param array $elements Key/value pairs to add to the URL + * + * @return string The new URL with the query strings added + * @since 1.7.0 */ +function elgg_http_add_url_query_elements($url, array $elements) { + $url_array = parse_url($url); + if (isset($url_array['query'])) { + $query = elgg_parse_str($url_array['query']); + } else { + $query = array(); + } + + foreach ($elements as $k => $v) { + $query[$k] = $v; + } + + $url_array['query'] = http_build_query($query); + $string = elgg_http_build_url($url_array, false); + + return $string; +} /** - * Gatekeeper function which ensures that a we are being executed from - * a specified location. - * - * To use, call this function with the function name (and optional file location) that it has to be called - * from, it will either return true or false. - * - * e.g. - * - * function my_secure_function() - * { - * if (!call_gatekeeper("my_call_function")) - * return false; + * Test if two URLs are functionally identical. * - * ... do secure stuff ... - * } + * @tip If $ignore_params is used, neither the name nor its value will be considered when comparing. * - * function my_call_function() - * { - * // will work - * my_secure_function(); - * } + * @tip The order of GET params doesn't matter. * - * function bad_function() - * { - * // Will not work - * my_secure_function(); - * } + * @param string $url1 First URL + * @param string $url2 Second URL + * @param array $ignore_params GET params to ignore in the comparison * - * @param mixed $function The function that this function must have in its call stack, - * to test against a method pass an array containing a class and method name. - * @param string $file Optional file that the function must reside in. + * @return bool + * @since 1.8.0 */ -function call_gatekeeper($function, $file = "") { - // Sanity check - if (!$function) { - return false; +function elgg_http_url_is_identical($url1, $url2, $ignore_params = array('offset', 'limit')) { + // if the server portion is missing but it starts with / then add the url in. + // @todo use elgg_normalize_url() + if (elgg_substr($url1, 0, 1) == '/') { + $url1 = elgg_get_site_url() . ltrim($url1, '/'); } - // Check against call stack to see if this is being called from the correct location - $callstack = debug_backtrace(); - $stack_element = false; + if (elgg_substr($url1, 0, 1) == '/') { + $url2 = elgg_get_site_url() . ltrim($url2, '/'); + } - foreach ($callstack as $call) { - if (is_array($function)) { - if ( - (strcmp($call['class'], $function[0]) == 0) && - (strcmp($call['function'], $function[1]) == 0) - ) { - $stack_element = $call; - } - } else { - if (strcmp($call['function'], $function) == 0) { - $stack_element = $call; - } + // @todo - should probably do something with relative URLs + + if ($url1 == $url2) { + return TRUE; + } + + $url1_info = parse_url($url1); + $url2_info = parse_url($url2); + + if (isset($url1_info['path'])) { + $url1_info['path'] = trim($url1_info['path'], '/'); + } + if (isset($url2_info['path'])) { + $url2_info['path'] = trim($url2_info['path'], '/'); + } + + // compare basic bits + $parts = array('scheme', 'host', 'path'); + + foreach ($parts as $part) { + if ((isset($url1_info[$part]) && isset($url2_info[$part])) + && $url1_info[$part] != $url2_info[$part]) { + return FALSE; + } elseif (isset($url1_info[$part]) && !isset($url2_info[$part])) { + return FALSE; + } elseif (!isset($url1_info[$part]) && isset($url2_info[$part])) { + return FALSE; } } - if (!$stack_element) { - return false; + // quick compare of get params + if (isset($url1_info['query']) && isset($url2_info['query']) + && $url1_info['query'] == $url2_info['query']) { + return TRUE; } + // compare get params that might be out of order + $url1_params = array(); + $url2_params = array(); - // If file then check that this it is being called from this function - if ($file) { - $mirror = null; + if (isset($url1_info['query'])) { + if ($url1_info['query'] = html_entity_decode($url1_info['query'])) { + $url1_params = elgg_parse_str($url1_info['query']); + } + } - if (is_array($function)) { - $mirror = new ReflectionMethod($function[0], $function[1]); - } else { - $mirror = new ReflectionFunction($function); + if (isset($url2_info['query'])) { + if ($url2_info['query'] = html_entity_decode($url2_info['query'])) { + $url2_params = elgg_parse_str($url2_info['query']); } + } - if ((!$mirror) || (strcmp($file,$mirror->getFileName())!=0)) { - return false; + // drop ignored params + foreach ($ignore_params as $param) { + if (isset($url1_params[$param])) { + unset($url1_params[$param]); + } + if (isset($url2_params[$param])) { + unset($url2_params[$param]); } } - return true; + // array_diff_assoc only returns the items in arr1 that aren't in arrN + // but not the items that ARE in arrN but NOT in arr1 + // if arr1 is an empty array, this function will return 0 no matter what. + // since we only care if they're different and not how different, + // add the results together to get a non-zero (ie, different) result + $diff_count = count(array_diff_assoc($url1_params, $url2_params)); + $diff_count += count(array_diff_assoc($url2_params, $url1_params)); + if ($diff_count > 0) { + return FALSE; + } + + return TRUE; } /** - * This function checks to see if it is being called at somepoint by a function defined somewhere - * on a given path (optionally including subdirectories). + * Checks for $array[$key] and returns its value if it exists, else + * returns $default. * - * This function is similar to call_gatekeeper() but returns true if it is being called by a method or function which has been defined on a given path or by a specified file. + * Shorthand for $value = (isset($array['key'])) ? $array['key'] : 'default'; * - * @param string $path The full path and filename that this function must have in its call stack If a partial path is given and $include_subdirs is true, then the function will return true if called by any function in or below the specified path. - * @param bool $include_subdirs Are subdirectories of the path ok, or must you specify an absolute path and filename. - * @param bool $strict_mode If true then the calling method or function must be directly called by something on $path, if false the whole call stack is searched. + * @param string $key The key to check. + * @param array $array The array to check against. + * @param mixed $default Default value to return if nothing is found. + * @param bool $strict Return array key if it's set, even if empty. If false, + * return $default if the array key is unset or empty. + * + * @return mixed + * @since 1.8.0 */ -function callpath_gatekeeper($path, $include_subdirs = true, $strict_mode = false) { - global $CONFIG; - - $path = sanitise_string($path); - - if ($path) { - $callstack = debug_backtrace(); +function elgg_extract($key, array $array, $default = null, $strict = true) { + if (!is_array($array)) { + return $default; + } - foreach ($callstack as $call) { - $call['file'] = str_replace("\\","/",$call['file']); + if ($strict) { + return (isset($array[$key])) ? $array[$key] : $default; + } else { + return (isset($array[$key]) && !empty($array[$key])) ? $array[$key] : $default; + } +} - if ($include_subdirs) { - if (strpos($call['file'], $path) === 0) { +/** + * Sorts a 3d array by specific element. + * + * @warning Will re-index numeric indexes. + * + * @note This operates the same as the built-in sort functions. + * It sorts the array and returns a bool for success. + * + * Do this: elgg_sort_3d_array_by_value($my_array); + * Not this: $my_array = elgg_sort_3d_array_by_value($my_array); + * + * @param array &$array Array to sort + * @param string $element Element to sort by + * @param int $sort_order PHP sort order + * {@see http://us2.php.net/array_multisort} + * @param int $sort_type PHP sort type + * {@see http://us2.php.net/sort} + * + * @return bool + */ +function elgg_sort_3d_array_by_value(&$array, $element, $sort_order = SORT_ASC, +$sort_type = SORT_LOCALE_STRING) { - if ($strict_mode) { - $callstack[1]['file'] = str_replace("\\","/",$callstack[1]['file']); - if ($callstack[1] === $call) { return true; } - } else { - return true; - } - } - } else { - if (strcmp($path, $call['file'])==0) { - if ($strict_mode) { - if ($callstack[1] === $call) { - return true; - } - } else { - return true; - } - } - } + $sort = array(); + foreach ($array as $v) { + if (isset($v[$element])) { + $sort[] = strtolower($v[$element]); + } else { + $sort[] = NULL; } - return false; - } - - if (isset($CONFIG->debug)) { - system_message("Gatekeeper'd function called from {$callstack[1]['file']}:{$callstack[1]['line']}\n\nStack trace:\n\n" . print_r($callstack, true)); - } + }; - return false; + return array_multisort($sort, $sort_order, $sort_type, $array); } /** - * Returns true or false depending on whether a PHP .ini setting is on or off + * Return the state of a php.ini setting as a bool + * + * @warning Using this on ini settings that are not boolean + * will be inaccurate! * * @param string $ini_get_arg The INI setting - * @return true|false Depending on whether it's on or off + * + * @return bool Depending on whether it's on or off */ function ini_get_bool($ini_get_arg) { - $temp = ini_get($ini_get_arg); + $temp = strtolower(ini_get($ini_get_arg)); - if ($temp == '1' or strtolower($temp) == 'on') { + if ($temp == '1' || $temp == 'on' || $temp == 'true') { return true; } return false; } /** + * Returns a PHP INI setting in bytes. + * + * @tip Use this for arithmetic when determining if a file can be uploaded. + * + * @param string $setting The php.ini setting + * + * @return int + * @since 1.7.0 + * @link http://www.php.net/manual/en/function.ini-get.php + */ +function elgg_get_ini_setting_in_bytes($setting) { + // retrieve INI setting + $val = ini_get($setting); + + // convert INI setting when shorthand notation is used + $last = strtolower($val[strlen($val) - 1]); + switch($last) { + case 'g': + $val *= 1024; + // fallthrough intentional + case 'm': + $val *= 1024; + // fallthrough intentional + case 'k': + $val *= 1024; + } + + // return byte value + return $val; +} + +/** + * Returns true is string is not empty, false, or null. + * * Function to be used in array_filter which returns true if $string is not null. * - * @param string $string + * @param string $string The string to test + * * @return bool + * @todo This is used once in metadata.php. Use a lambda function instead. */ function is_not_null($string) { - if (($string==='') || ($string===false) || ($string===null)) { + if (($string === '') || ($string === false) || ($string === null)) { return false; } return true; } - /** - * Normalise the singular keys in an options array - * to the plural keys. + * Normalise the singular keys in an options array to plural keys. + * + * Used in elgg_get_entities*() functions to support shortcutting plural + * names by singular names. + * + * @param array $options The options array. $options['keys'] = 'values'; + * @param array $singulars A list of singular words to pluralize by adding 's'. * - * @param $options - * @param $singulars * @return array + * @since 1.7.0 + * @access private */ function elgg_normalise_plural_options_array($options, $singulars) { foreach ($singulars as $singular) { $plural = $singular . 's'; - // normalize the singular to plural - // isset() returns FALSE for array values of NULL, so they are ignored. - // everything else falsy is included. - //if (isset($options[$singular]) && $options[$singular] !== NULL && $options[$singular] !== FALSE) { - if (isset($options[$singular])) { - if (isset($options[$plural])) { - if (is_array($options[$plural])) { - $options[$plural][] = $options[$singlar]; + if (array_key_exists($singular, $options)) { + if ($options[$singular] === ELGG_ENTITIES_ANY_VALUE) { + $options[$plural] = $options[$singular]; + } else { + // Test for array refs #2641 + if (!is_array($options[$singular])) { + $options[$plural] = array($options[$singular]); } else { - $options[$plural] = array($options[$plural], $options[$singular]); + $options[$plural] = $options[$singular]; } - } else { - $options[$plural] = array($options[$singular]); } } + unset($options[$singular]); } @@ -2367,411 +1754,551 @@ function elgg_normalise_plural_options_array($options, $singulars) { } /** - * Get the full URL of the current page. + * Emits a shutdown:system event upon PHP shutdown, but before database connections are dropped. * - * @return string The URL + * @tip Register for the shutdown:system event to perform functions at the end of page loads. + * + * @warning Using this event to perform long-running functions is not very + * useful. Servers will hold pages until processing is done before sending + * them out to the browser. + * + * @see http://www.php.net/register-shutdown-function + * + * @return void + * @see register_shutdown_hook() + * @access private */ -function full_url() { - $s = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : ""; - $protocol = substr(strtolower($_SERVER["SERVER_PROTOCOL"]), 0, strpos(strtolower($_SERVER["SERVER_PROTOCOL"]), "/")) . $s; - $port = ($_SERVER["SERVER_PORT"] == "80") ? "" : (":".$_SERVER["SERVER_PORT"]); - return $protocol . "://" . $_SERVER['SERVER_NAME'] . $port . $_SERVER['REQUEST_URI']; -} - -/** - * Useful function found in the comments on the PHP man page for ip2long. - * Returns 1 if an IP matches a given range. - * - * TODO: Check licence... assuming this is PD since it was found several places on the interwebs.. - * please check or rewrite. - * - * Matches: - * xxx.xxx.xxx.xxx (exact) - * xxx.xxx.xxx.[yyy-zzz] (range) - * xxx.xxx.xxx.xxx/nn (nn = # bits, cisco style -- i.e. /24 = class C) - * Does not match: - * xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported) - */ -function test_ip($range, $ip) { - $result = 1; - - # IP Pattern Matcher - # J.Adams <jna@retina.net> - # - # Matches: - # - # xxx.xxx.xxx.xxx (exact) - # xxx.xxx.xxx.[yyy-zzz] (range) - # xxx.xxx.xxx.xxx/nn (nn = # bits, cisco style -- i.e. /24 = class C) - # - # Does not match: - # xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported) - - if (ereg("([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/([0-9]+)",$range,$regs)) { - # perform a mask match - $ipl = ip2long($ip); - $rangel = ip2long($regs[1] . "." . $regs[2] . "." . $regs[3] . "." . $regs[4]); - - $maskl = 0; - - for ($i = 0; $i< 31; $i++) { - if ($i < $regs[5]-1) { - $maskl = $maskl + pow(2,(30-$i)); - } - } +function _elgg_shutdown_hook() { + global $START_MICROTIME; - if (($maskl & $rangel) == ($maskl & $ipl)) { - return 1; - } else { - return 0; - } - } else { - # range based - $maskocts = split("\.",$range); - $ipocts = split("\.",$ip); - - # perform a range match - for ($i=0; $i<4; $i++) { - if (ereg("\[([0-9]+)\-([0-9]+)\]",$maskocts[$i],$regs)) { - if ( ($ipocts[$i] > $regs[2]) || ($ipocts[$i] < $regs[1])) { - $result = 0; - } - } else { - if ($maskocts[$i] <> $ipocts[$i]) { - $result = 0; - } - } - } + try { + elgg_trigger_event('shutdown', 'system'); + + $time = (float)(microtime(TRUE) - $START_MICROTIME); + // demoted to NOTICE from DEBUG so javascript is not corrupted + elgg_log("Page {$_SERVER['REQUEST_URI']} generated in $time seconds", 'NOTICE'); + } catch (Exception $e) { + $message = 'Error: ' . get_class($e) . ' thrown within the shutdown handler. '; + $message .= "Message: '{$e->getMessage()}' in file {$e->getFile()} (line {$e->getLine()})"; + error_log($message); + error_log("Exception trace stack: {$e->getTraceAsString()}"); } +} - return $result; +/** + * Serve javascript pages. + * + * Searches for views under js/ and outputs them with special + * headers for caching control. + * + * @param array $page The page array + * + * @return bool + * @elgg_pagehandler js + * @access private + */ +function elgg_js_page_handler($page) { + return elgg_cacheable_view_page_handler($page, 'js'); } /** - * Match an IP address against a number of ip addresses or ranges, returning true if found. + * Serve individual views for Ajax. + * + * /ajax/view/<name of view>?<key/value params> + * + * @param array $page The page array * - * @param array $networks - * @param string $ip * @return bool + * @elgg_pagehandler ajax + * @access private */ -function is_ip_in_array(array $networks, $ip) { - global $SYSTEM_LOG; +function elgg_ajax_page_handler($page) { + if (is_array($page) && sizeof($page)) { + // throw away 'view' and form the view name + unset($page[0]); + $view = implode('/', $page); + + $allowed_views = elgg_get_config('allowed_ajax_views'); + if (!array_key_exists($view, $allowed_views)) { + header('HTTP/1.1 403 Forbidden'); + exit; + } - foreach ($networks as $network) { - if (test_ip(trim($network), $ip)) { - return true; + // pull out GET parameters through filter + $vars = array(); + foreach ($_GET as $name => $value) { + $vars[$name] = get_input($name); } - } + if (isset($vars['guid'])) { + $vars['entity'] = get_entity($vars['guid']); + } + + echo elgg_view($view, $vars); + return true; + } return false; } /** - * An interface for objects that behave as elements within a social network that have a profile. - * - */ -interface Friendable { - /** - * Adds a user as a friend - * - * @param int $friend_guid The GUID of the user to add - */ - public function addFriend($friend_guid); - - /** - * Removes a user as a friend - * - * @param int $friend_guid The GUID of the user to remove - */ - public function removeFriend($friend_guid); - - /** - * Determines whether or not the current user is a friend of this entity - * - */ - public function isFriend(); - - /** - * Determines whether or not this entity is friends with a particular entity - * - * @param int $user_guid The GUID of the entity this entity may or may not be friends with - */ - public function isFriendsWith($user_guid); - - /** - * Determines whether or not a foreign entity has made this one a friend - * - * @param int $user_guid The GUID of the foreign entity - */ - public function isFriendOf($user_guid); - - /** - * Returns this entity's friends - * - * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset - */ - public function getFriends($subtype = "", $limit = 10, $offset = 0); - - /** - * Returns entities that have made this entity a friend - * - * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset - */ - public function getFriendsOf($subtype = "", $limit = 10, $offset = 0); - - /** - * Returns objects in this entity's container - * - * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset - */ - public function getObjects($subtype="", $limit = 10, $offset = 0); - - /** - * Returns objects in the containers of this entity's friends - * - * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset - */ - public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0); - - /** - * Returns the number of object entities in this entity's container - * - * @param string $subtype The subtype of entity to count - */ - public function countObjects($subtype = ""); -} - -/** - * Rebuilds a parsed (partial) URL - * - * @param array $parts Associative array of URL components like parse_url() returns - * @return str Full URL - * @since 1.7 + * Serve CSS + * + * Serves CSS from the css views directory with headers for caching control + * + * @param array $page The page array + * + * @return bool + * @elgg_pagehandler css + * @access private */ -function elgg_http_build_url(array $parts) { - // build only what's given to us. - $scheme = isset($parts['scheme']) ? "{$parts['scheme']}://" : ''; - $host = isset($parts['host']) ? "{$parts['host']}" : ''; - $port = isset($parts['port']) ? ":{$parts['port']}" : ''; - $path = isset($parts['path']) ? "{$parts['path']}" : ''; - $query = isset($parts['query']) ? "?{$parts['query']}" : ''; - - $string = $scheme . $host . $port . $path . $query; - - return $string; +function elgg_css_page_handler($page) { + if (!isset($page[0])) { + // default css + $page[0] = 'elgg'; + } + + return elgg_cacheable_view_page_handler($page, 'css'); } - /** - * Adds action tokens to URL + * Serves a JS or CSS view with headers for caching. * - * @param str $link Full action URL - * @return str URL with action tokens - * @since 1.7 + * /<css||js>/name/of/view.<last_cache>.<css||js> + * + * @param array $page The page array + * @param string $type The type: js or css + * + * @return bool + * @access private */ -function elgg_add_action_tokens_to_url($url) { - $components = parse_url($url); +function elgg_cacheable_view_page_handler($page, $type) { - if (isset($components['query'])) { - $query = elgg_parse_str($components['query']); - } else { - $query = array(); - } + switch ($type) { + case 'js': + $content_type = 'text/javascript'; + break; - if (isset($query['__elgg_ts']) && isset($query['__elgg_token'])) { - return $url; + case 'css': + $content_type = 'text/css'; + break; + + default: + return false; + break; } - // append action tokens to the existing query - $query['__elgg_ts'] = time(); - $query['__elgg_token'] = generate_action_token($query['__elgg_ts']); - $components['query'] = http_build_query($query); + if ($page) { + // the view file names can have multiple dots + // eg: views/default/js/calendars/jquery.fullcalendar.min.php + // translates to the url /js/calendars/jquery.fullcalendar.min.<ts>.js + // and the view js/calendars/jquery.fullcalendar.min + // we ignore the last two dots for the ts and the ext. + // Additionally, the timestamp is optional. + $page = implode('/', $page); + $regex = '|(.+?)\.([\d]+\.)?\w+$|'; + preg_match($regex, $page, $matches); + $view = $matches[1]; + $return = elgg_view("$type/$view"); - // rebuild the full url - return elgg_http_build_url($components); -} + header("Content-type: $content_type"); -/** - * @deprecated 1.7 final - */ -function elgg_validate_action_url($url) { - elgg_deprecated_notice('elgg_validate_action_url had a short life. Use elgg_add_action_tokens_to_url() instead.', '1.7b'); + // @todo should js be cached when simple cache turned off + //header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', strtotime("+10 days")), true); + //header("Pragma: public"); + //header("Cache-Control: public"); + //header("Content-Length: " . strlen($return)); - return elgg_add_action_tokens_to_url($url); + echo $return; + return true; + } + return false; } /** - * Removes a single elementry from a (partial) url query. + * Reverses the ordering in an ORDER BY clause. This is achived by replacing + * asc with desc, or appending desc to the end of the clause. * - * @param string $url - * @param string $element + * This is used mostly for elgg_get_entities() and other similar functions. + * + * @param string $order_by An order by clause + * @access private * @return string + * @access private */ -function elgg_http_remove_url_query_element($url, $element) { - $url_array = parse_url($url); +function elgg_sql_reverse_order_by_clause($order_by) { + $order_by = strtolower($order_by); - if (isset($url_array['query'])) { - $query = elgg_parse_str($url_array['query']); + if (strpos($order_by, ' asc') !== false) { + $return = str_replace(' asc', ' desc', $order_by); + } elseif (strpos($order_by, ' desc') !== false) { + $return = str_replace(' desc', ' asc', $order_by); } else { - // nothing to remove. Return original URL. - return $url; + // no order specified, so default to desc since mysql defaults to asc + $return = $order_by . ' desc'; } - if (array_key_exists($element, $query)) { - unset($query[$element]); - } + return $return; +} - $url_array['query'] = http_build_query($query); - $string = elgg_http_build_url($url_array); - return $string; +/** + * Enable objects with an enable() method. + * + * Used as a callback for ElggBatch. + * + * @todo why aren't these static methods on ElggBatch? + * + * @param object $object The object to enable + * @return bool + * @access private + */ +function elgg_batch_enable_callback($object) { + // our db functions return the number of rows affected... + return $object->enable() ? true : false; } +/** + * Disable objects with a disable() method. + * + * Used as a callback for ElggBatch. + * + * @param object $object The object to disable + * @return bool + * @access private + */ +function elgg_batch_disable_callback($object) { + // our db functions return the number of rows affected... + return $object->disable() ? true : false; +} /** - * Adds get params to $url + * Delete objects with a delete() method. + * + * Used as a callback for ElggBatch. * - * @param str $url - * @param array $elements k/v pairs. - * @return str + * @param object $object The object to disable + * @return bool + * @access private */ -function elgg_http_add_url_query_elements($url, array $elements) { - $url_array = parse_url($url); +function elgg_batch_delete_callback($object) { + // our db functions return the number of rows affected... + return $object->delete() ? true : false; +} - if (isset($url_array['query'])) { - $query = elgg_parse_str($url_array['query']); - } else { - $query = array(); +/** + * Checks if there are some constraints on the options array for + * potentially dangerous operations. + * + * @param array $options Options array + * @param string $type Options type: metadata or annotations + * @return bool + * @access private + */ +function elgg_is_valid_options_for_batch_operation($options, $type) { + if (!$options || !is_array($options)) { + return false; } - foreach ($elements as $k => $v) { - $query[$k] = $v; + // at least one of these is required. + $required = array( + // generic restraints + 'guid', 'guids' + ); + + switch ($type) { + case 'metadata': + $metadata_required = array( + 'metadata_owner_guid', 'metadata_owner_guids', + 'metadata_name', 'metadata_names', + 'metadata_value', 'metadata_values' + ); + + $required = array_merge($required, $metadata_required); + break; + + case 'annotations': + case 'annotation': + $annotations_required = array( + 'annotation_owner_guid', 'annotation_owner_guids', + 'annotation_name', 'annotation_names', + 'annotation_value', 'annotation_values' + ); + + $required = array_merge($required, $annotations_required); + break; + + default: + return false; } - $url_array['query'] = http_build_query($query); - $string = elgg_http_build_url($url_array); + foreach ($required as $key) { + // check that it exists and is something. + if (isset($options[$key]) && $options[$key]) { + return true; + } + } - return $string; + return false; } /** - * Returns the PHP INI setting in bytes + * Intercepts the index page when Walled Garden mode is enabled. * - * @param str $setting - * @return int - * @since 1.7 - * @link http://www.php.net/manual/en/function.ini-get.php + * @link http://docs.elgg.org/Tutorials/WalledGarden + * @elgg_plugin_hook index system + * + * @param string $hook The name of the hook + * @param string $type The type of hook + * @param bool $value Has a plugin already rendered an index page? + * @param array $params Array of parameters (should be empty) + * @return bool + * @access private */ -function elgg_get_ini_setting_in_bytes($setting) { - // retrieve INI setting - $val = ini_get($setting); - - // convert INI setting when shorthand notation is used - $last = strtolower($val[strlen($val)-1]); - switch($last) { - case 'g': - $val *= 1024; - case 'm': - $val *= 1024; - case 'k': - $val *= 1024; +function elgg_walled_garden_index($hook, $type, $value, $params) { + if ($value) { + // do not create a second index page so return + return; } - // return byte value - return $val; + elgg_load_css('elgg.walled_garden'); + elgg_load_js('elgg.walled_garden'); + + $content = elgg_view('core/walled_garden/login'); + + $params = array( + 'content' => $content, + 'class' => 'elgg-walledgarden-double', + 'id' => 'elgg-walledgarden-login', + ); + $body = elgg_view_layout('walled_garden', $params); + echo elgg_view_page('', $body, 'walled_garden'); + + // return true to prevent other plugins from adding a front page + return true; +} + +/** + * Serve walled garden sections + * + * @param array $page Array of URL segments + * @return string + * @access private + */ +function _elgg_walled_garden_ajax_handler($page) { + $view = $page[0]; + $params = array( + 'content' => elgg_view("core/walled_garden/$view"), + 'class' => 'elgg-walledgarden-single hidden', + 'id' => str_replace('_', '-', "elgg-walledgarden-$view"), + ); + echo elgg_view_layout('walled_garden', $params); + return true; } /** - * Server javascript pages. + * Checks the status of the Walled Garden and forwards to a login page + * if required. * - * @param $page - * @return unknown_type + * If the site is in Walled Garden mode, all page except those registered as + * plugin pages by {@elgg_hook public_pages walled_garden} will redirect to + * a login page. + * + * @since 1.8.0 + * @elgg_event_handler init system + * @link http://docs.elgg.org/Tutorials/WalledGarden + * @return void + * @access private */ -function js_page_handler($page) { - if (is_array($page) && sizeof($page)) { - $js = str_replace('.js','',$page[0]); - $return = elgg_view('js/' . $js); +function elgg_walled_garden() { + global $CONFIG; - header('Content-type: text/javascript'); - header('Expires: ' . date('r',time() + 864000)); - header("Pragma: public"); - header("Cache-Control: public"); - header("Content-Length: " . strlen($return)); + elgg_register_css('elgg.walled_garden', '/css/walled_garden.css'); + elgg_register_js('elgg.walled_garden', '/js/walled_garden.js'); - echo $return; - exit; + elgg_register_page_handler('walled_garden', '_elgg_walled_garden_ajax_handler'); + + // check for external page view + if (isset($CONFIG->site) && $CONFIG->site instanceof ElggSite) { + $CONFIG->site->checkWalledGarden(); } } /** - * This function is a shutdown hook registered on startup which does nothing more than trigger a - * shutdown event when the script is shutting down, but before database connections have been dropped etc. + * Remove public access for walled gardens * + * @param string $hook + * @param string $type + * @param array $accesses + * @return array + * @access private */ -function __elgg_shutdown_hook() { - global $START_MICROTIME; - - trigger_elgg_event('shutdown', 'system'); - - $time = (float)(microtime(TRUE) - $START_MICROTIME); - elgg_log("Page {$_SERVER['REQUEST_URI']} generated in $time seconds", 'DEBUG'); +function _elgg_walled_garden_remove_public_access($hook, $type, $accesses) { + if (isset($accesses[ACCESS_PUBLIC])) { + unset($accesses[ACCESS_PUBLIC]); + } + return $accesses; } /** - * Register functions for Elgg core + * Boots the engine * - * @return unknown_type + * 1. sets error handlers + * 2. connects to database + * 3. verifies the installation suceeded + * 4. loads application configuration + * 5. loads i18n data + * 6. loads site configuration + * + * @access private */ -function elgg_init() { - // Page handler for JS - register_page_handler('js','js_page_handler'); +function _elgg_engine_boot() { + // Register the error handlers + set_error_handler('_elgg_php_error_handler'); + set_exception_handler('_elgg_php_exception_handler'); + + setup_db_connections(); + + verify_installation(); + + _elgg_load_application_config(); + + _elgg_load_site_config(); - // Register an event triggered at system shutdown - register_shutdown_function('__elgg_shutdown_hook'); + _elgg_session_boot(); + + _elgg_load_cache(); + + _elgg_load_translations(); } /** - * Boot Elgg - * @return unknown_type + * Elgg's main init. + * + * Handles core actions for comments, the JS pagehandler, and the shutdown function. + * + * @elgg_event_handler init system + * @return void + * @access private */ -function elgg_boot() { - // Actions - register_action('comments/add'); - register_action('comments/delete'); +function elgg_init() { + global $CONFIG; - elgg_view_register_simplecache('css'); - elgg_view_register_simplecache('js/friendsPickerv1'); - elgg_view_register_simplecache('js/initialise_elgg'); + elgg_register_action('comments/add'); + elgg_register_action('comments/delete'); + + elgg_register_page_handler('js', 'elgg_js_page_handler'); + elgg_register_page_handler('css', 'elgg_css_page_handler'); + elgg_register_page_handler('ajax', 'elgg_ajax_page_handler'); + + elgg_register_js('elgg.autocomplete', 'js/lib/ui.autocomplete.js'); + elgg_register_js('jquery.ui.autocomplete.html', 'vendors/jquery/jquery.ui.autocomplete.html.js'); + elgg_register_js('elgg.userpicker', 'js/lib/ui.userpicker.js'); + elgg_register_js('elgg.friendspicker', 'js/lib/ui.friends_picker.js'); + elgg_register_js('jquery.easing', 'vendors/jquery/jquery.easing.1.3.packed.js'); + elgg_register_js('elgg.avatar_cropper', 'js/lib/ui.avatar_cropper.js'); + elgg_register_js('jquery.imgareaselect', 'vendors/jquery/jquery.imgareaselect-0.9.8/scripts/jquery.imgareaselect.min.js'); + elgg_register_js('elgg.ui.river', 'js/lib/ui.river.js'); + + elgg_register_css('jquery.imgareaselect', 'vendors/jquery/jquery.imgareaselect-0.9.8/css/imgareaselect-deprecated.css'); + + // Trigger the shutdown:system event upon PHP shutdown. + register_shutdown_function('_elgg_shutdown_hook'); + + $logo_url = elgg_get_site_url() . "_graphics/elgg_toolbar_logo.gif"; + elgg_register_menu_item('topbar', array( + 'name' => 'elgg_logo', + 'href' => 'http://www.elgg.org/', + 'text' => "<img src=\"$logo_url\" alt=\"Elgg logo\" width=\"38\" height=\"20\" />", + 'priority' => 1, + 'link_class' => 'elgg-topbar-logo', + )); + + // Sets a blacklist of words in the current language. + // This is a comma separated list in word:blacklist. + // @todo possibly deprecate + $CONFIG->wordblacklist = array(); + $list = explode(',', elgg_echo('word:blacklist')); + if ($list) { + foreach ($list as $l) { + $CONFIG->wordblacklist[] = trim($l); + } + } } /** - * Runs unit tests for the API. + * Adds unit tests for the general API. + * + * @param string $hook unit_test + * @param string $type system + * @param array $value array of test files + * @param array $params empty + * + * @elgg_plugin_hook unit_tests system + * @return array + * @access private */ function elgg_api_test($hook, $type, $value, $params) { global $CONFIG; $value[] = $CONFIG->path . 'engine/tests/api/entity_getter_functions.php'; + $value[] = $CONFIG->path . 'engine/tests/api/helpers.php'; + $value[] = $CONFIG->path . 'engine/tests/regression/trac_bugs.php'; return $value; } -/** - * Some useful constant definitions +/**#@+ + * Controls access levels on ElggEntity entities, metadata, and annotations. + * + * @warning ACCESS_DEFAULT is a place holder for the input/access view. Do not + * use it when saving an entity. + * + * @var int */ define('ACCESS_DEFAULT', -1); define('ACCESS_PRIVATE', 0); define('ACCESS_LOGGED_IN', 1); define('ACCESS_PUBLIC', 2); define('ACCESS_FRIENDS', -2); +/**#@-*/ +/** + * Constant to request the value of a parameter be ignored in elgg_get_*() functions + * + * @see elgg_get_entities() + * @var NULL + * @since 1.7 + */ define('ELGG_ENTITIES_ANY_VALUE', NULL); + +/** + * Constant to request the value of a parameter be nothing in elgg_get_*() functions. + * + * @see elgg_get_entities() + * @var int 0 + * @since 1.7 + */ define('ELGG_ENTITIES_NO_VALUE', 0); -register_elgg_event_handler('init', 'system', 'elgg_init'); -register_elgg_event_handler('boot', 'system', 'elgg_boot', 1000); -register_plugin_hook('unit_test', 'system', 'elgg_api_test');
\ No newline at end of file +/** + * Used in calls to forward() to specify the browser should be redirected to the + * referring page. + * + * @see forward + * @var int -1 + */ +define('REFERRER', -1); + +/** + * Alternate spelling for REFERRER. Included because of some bad documentation + * in the original HTTP spec. + * + * @see forward() + * @link http://en.wikipedia.org/wiki/HTTP_referrer#Origin_of_the_term_referer + * @var int -1 + */ +define('REFERER', -1); + +elgg_register_event_handler('init', 'system', 'elgg_init'); +elgg_register_event_handler('boot', 'system', '_elgg_engine_boot', 1); +elgg_register_plugin_hook_handler('unit_test', 'system', 'elgg_api_test'); + +elgg_register_event_handler('init', 'system', 'add_custom_menu_items', 1000); +elgg_register_event_handler('init', 'system', 'elgg_walled_garden', 1000); diff --git a/engine/lib/entities.php b/engine/lib/entities.php index d52b86005..4fcf1c657 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -1,1274 +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; - } - - /** - * Clone an entity - * - * Resets the guid so that the entity can be saved as a distinct entity from - * the original. Creation time will be set when this new entity is saved. - * The owner and container guids come from the original entity. The clone - * method copies metadata but does not copy over annotations, or private settings. - * - * Note: metadata will have its owner and access id set when the entity is saved - * and it will be the same as that off the entity. - */ - public function __clone() { - - $orig_entity = get_entity($this->guid); - if (!$orig_entity) { - elgg_log("Failed to clone entity with GUID $this->guid", "ERROR"); - return; - } - - $metadata_array = get_metadata_for_entity($this->guid); - - $this->attributes['guid'] = ""; - - // copy metadata over to new entity - slightly convoluted due to - // handling of metadata arrays - if (is_array($metadata_array)) { - // create list of metadata names - $metadata_names = array(); - foreach ($metadata_array as $metadata) { - $metadata_names[] = $metadata['name']; - } - // arrays are stored with multiple enties per name - $metadata_names = array_unique($metadata_names); - - // move the metadata over - foreach ($metadata_names as $name) { - $this->set($name, $orig_entity->$name); - } - } - } - - /** - * Return the value of a 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. - * - * subtype is returned as an id rather than the subtype string. Use getSubtype() - * to get the subtype string. - * - * @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); - - // getMetaData returns NULL if $name is not found - return $meta; - } - - /** - * 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) { - return $this->$name !== NULL; - } - - /** - * 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 Name of the metadata - * @param mixed $value Value of the metadata - * @param string $value_type Types supported: integer and string. Will auto-identify if not set - * @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. - * @return bool - */ - public function addRelationship($guid, $relationship) { - return add_entity_relationship($this->getGUID(), $relationship, $guid); - } - - /** - * Remove a relationship - * - * @param int $guid - * @param str $relationship - * @return bool - */ - public function removeRelationship($guid, $relationship) { - return remove_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 elgg_get_entities_from_relationship(array( - 'relationship' => $relationship, - 'relationship_guid' => $this->getGUID(), - 'inverse_relationship' => $inverse, - 'limit' => $limit, - 'offset' => $offset - )); - } - - /** - * Gets the number of of entities from a specific relationship type - * - * @param string $relationship Relationship type (eg "friends") - * @param bool $inverse_relationship - * @return int|false The number of entities or false on failure - */ - function countEntitiesFromRelationship($relationship, $inverse_relationship = FALSE) { - return elgg_get_entities_from_relationship(array( - 'relationship' => $relationship, - 'relationship_guid' => $this->getGUID(), - 'inverse_relationship' => $inverse_relationship, - 'count' => TRUE - )); - } - - /** - * 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 container. - * - * @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]); - } - } - - // set the subtype to id now rather than a string - $this->attributes['subtype'] = get_subtype_id($this->attributes['type'], $this->attributes['subtype']); - - // 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; - - 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', - ); - } +global $ENTITY_CACHE; +$ENTITY_CACHE = array(); - /** - * 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; - } - - /** - * Returns tags for this entity. - * - * @param array $tag_names Optionally restrict by tag metadata names. - * @return array - */ - public function getTags($tag_names = NULL) { - global $CONFIG; - - if ($tag_names && !is_array($tag_names)) { - $tag_names = array($tag_names); - } - - $valid_tags = elgg_get_registered_tag_metadata_names(); - $entity_tags = array(); - - foreach ($valid_tags as $tag_name) { - if (is_array($tag_names) && !in_array($tag_name, $tag_names)) { - continue; - } - - if ($tags = $this->$tag_name) { - // if a single tag, metadata returns a string. - // if multiple tags, metadata returns an array. - if (is_array($tags)) { - $entity_tags = array_merge($entity_tags, $tags); - } else { - $entity_tags[] = $tags; - } - } - } - - return $entity_tags; - } - - // 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; + + // 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); - $ENTITY_CACHE[$entity->guid] = $entity; + 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]; @@ -1279,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; +} + +/** + * 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; - $SUBTYPE_CACHE[$subtype_id] = $result; - return $result->subtype; + 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. + * + * @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 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) + * @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 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 * - * @param int $guid - * @param int $owner_guid - * @param int $access_id - * @param int $container_guid + * @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; @@ -1481,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 @@ -1495,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; } @@ -1509,27 +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); @@ -1537,41 +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), $return); + 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; @@ -1580,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; @@ -1621,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)) { @@ -1645,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); } } @@ -1682,51 +748,134 @@ 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; + } + } + + // don't let incomplete entities cause fatal exceptions + try { + $new_entity = entity_row_to_elggstar($entity_row); + } catch (IncompleteEntityException $e) { + return false; } - return entity_row_to_elggstar(get_entity_as_row($guid)); + 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. NB: Plural arguments can be written as - * singular if only specifying a single element. (e.g., 'type' => 'object' - * vs 'types' => array('object')). + * 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 (SQL: type = '$type' OR...see below...) + * 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 (SQL: subtype = '$subtype'...see above) + * type_subtype_pairs => NULL|ARR (array('type' => 'subtype')) + * (type = '$type' AND subtype = '$subtype') pairs * - * type_subtype_pairs => NULL|ARR (array('type' => 'subtype')) (SQL: 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 * @@ -1744,8 +893,16 @@ function get_entity($guid) { * * joins => array() Additional joins * - * @return if count, int - * if not count, array or false if no entities + * 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; @@ -1755,6 +912,7 @@ function elgg_get_entities(array $options = array()) { '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, @@ -1764,6 +922,7 @@ function elgg_get_entities(array $options = array()) { '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, @@ -1771,13 +930,27 @@ function elgg_get_entities(array $options = array()) { 'count' => FALSE, 'selects' => array(), 'wheres' => array(), - 'joins' => 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 @@ -1787,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) { @@ -1807,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']); @@ -1827,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 = ''; @@ -1853,24 +1030,53 @@ 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 ($options['group_by'] = sanitise_string($options['group_by'])) { + if ($options['group_by']) { $query .= " GROUP BY {$options['group_by']}"; } - if ($options['order_by'] = sanitise_string($options['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); @@ -1879,96 +1085,114 @@ function elgg_get_entities(array $options = array()) { } /** - * @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_deprecated_notice('get_entities() was deprecated by elgg_get_entities().', 1.7); - // 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; - } - } - - if ($subtype) { - if (is_array($subtype)) { - $options['subtypes'] = $subtype; - } else { - $options['subtype'] = $subtype; - } - } - - if ($owner_guid) { - if (is_array($owner_guid)) { - $options['owner_guids'] = $owner_guid; - } else { - $options['owner_guid'] = $owner_guid; - } - } - - if ($order_by) { - $options['order_by'] = $order_by; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } +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 ($count) { - $options['count'] = $count; - } + $rows = get_data($sql); - if ($site_guid) { - $options['site_guids'] = $site_guid; - } + // guids to look up in each type + $lookup_types = array(); + // maps GUIDs to the $rows key + $guid_to_key = array(); - if ($container_guid) { - $options['container_guids'] = $container_guid; + 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(); } - if ($timeupper) { - $options['time_upper'] = $timeupper; + // 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 ($timelower) { - $options['time_lower'] = $timelower; + // Second pass to finish conversion + foreach ($rows as $i => $row) { + if ($row instanceof ElggEntity) { + continue; + } else { + 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); + } + } + } } - - $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. @@ -1982,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(); @@ -2009,7 +1233,7 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair foreach ($types as $type) { if (!in_array($type, $valid_types)) { $valid_types_count--; - unset ($types[array_search($type, $types)]); + unset($types[array_search($type, $types)]); } else { // do the checking (and decrementing) in the subtype section. $valid_subtypes_count += count($subtypes); @@ -2027,13 +1251,24 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair $subtype_ids = array(); if ($subtypes) { foreach ($subtypes as $subtype) { - // check that the subtype is valid (with ELGG_ENTITIES_NO_VALUE being a valid subtype) - if (ELGG_ENTITIES_NO_VALUE === $subtype || $subtype_id = get_subtype_id($type, $subtype)) { - $subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $subtype) ? ELGG_ENTITIES_NO_VALUE : $subtype_id; - } else { - $valid_subtypes_count--; - 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; + } } } @@ -2061,7 +1296,7 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair foreach ($pairs as $paired_type => $paired_subtypes) { if (!in_array($paired_type, $valid_types)) { $valid_pairs_count--; - unset ($pairs[array_search($paired_type, $pairs)]); + unset($pairs[array_search($paired_type, $pairs)]); } else { if ($paired_subtypes && !is_array($paired_subtypes)) { $pairs[$paired_type] = array($paired_subtypes); @@ -2079,11 +1314,14 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair if (is_array($paired_subtypes)) { $paired_subtype_ids = array(); foreach ($paired_subtypes as $paired_subtype) { - 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; + 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 { $valid_pairs_subtypes_count--; - elgg_log("Type-subtype $paired_type:$paired_subtype' does not exist!", 'WARNING'); + 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; } @@ -2096,7 +1334,8 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair 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')"; @@ -2113,78 +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. + * + * @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. * - * @todo Probably DRY up once things are settled. - * @param str $table - * @param NULL|array $owner_guids - * @return FALSE|str + * @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 = ''; - - // implode(',', 0) returns 0. - if (($owner_str = implode(',', $owner_guids_sanitised)) && ($owner_str !== FALSE) && ($owner_str !== '')) { - $where = "({$table}.owner_guid IN ($owner_str))"; - } + $guids_sanitized = array(); + foreach ($guids as $guid) { + if ($guid !== ELGG_ENTITIES_NO_VALUE) { + $guid = sanitise_int($guid); - 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; @@ -2193,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(); @@ -2232,144 +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_guid))) { - 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 + * Displays list of entities with formatting specified + * by the entity view. * - * @see elgg_view_entity_list + * @tip Pagination is handled automatically. * - * @param array $options Any elgg_get_entity() options plus: - * - * 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); - - return elgg_view_entity_list($entities, $count, $options['offset'], - $options['limit'], $options['full_view'], $options['view_type_toggle'], $options['pagination']); -} +function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entities', + $viewer = 'elgg_view_entity_list') { -/** - * @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_deprecated_notice('list_entities() was deprecated by elgg_list_entities()!', 1.7); + 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'] = $viewtypetoggle; - $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. - * - * @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) - * @param true|false $viewtypetoggle Whether or not to allow gallery view (default: true) - * @param true|false $pagination Whether to display pagination (default: true) - * @return string A viewable list of entities + * Returns a list of months in which entities were updated or created. + * + * @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 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 list_entities_groups($subtype = "", $owner_guid = 0, $container_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = 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); +function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0, +$order_by = 'time_created') { - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); -} - -/** - * Returns a list of months containing content specified by the parameters - * - * @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 - * @param str order_by SQL order by clause - * @return array|false Either an array of timestamps, or false on failure - */ -function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0, $order_by = 'time_created') { global $CONFIG; $site_guid = (int) $site_guid; @@ -2386,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})"; } } @@ -2415,10 +1569,10 @@ function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_ 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}"; @@ -2441,7 +1595,7 @@ function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_ $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; @@ -2450,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; @@ -2462,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; } @@ -2500,86 +1676,155 @@ function disable_entity($guid, $reason = "", $recursive = true) { } /** - * Enable an entity again. + * Enable an entity. * - * @param int $guid + * @warning In order to enable an entity, you must first use + * {@link access_show_hidden_entities()}. + * + * @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"); - return $result; + $entity->deleteMetadata('disable_reason'); + $entity->enableMetadata(); + $entity->enableAnnotations(); + + 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, $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])) { - invalidate_cache_for_entity($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 = ""; @@ -2605,7 +1850,7 @@ function delete_entity($guid, $recursive = true) { } } - return $res; + return (bool)$res; } } } @@ -2614,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_deprecated_notice('delete_entities() was deprecated because no one should use it.', 1.7); - 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']; @@ -2664,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 @@ -2681,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(); @@ -2698,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'); @@ -2713,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); @@ -2739,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; @@ -2757,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); @@ -2771,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 @@ -2788,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. * - * 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 + * plugin hook. * - * @see register_plugin_hook + * @warning If a $user_guid is not passed it will default to the logged in user. + * + * @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 ($user) { - if ($entity->getOwner() == $user->getGUID()) { + if ($entity->getOwnerGUID() == $user->getGUID()) { $return = true; } if ($entity->container_guid == $user->getGUID()) { @@ -2823,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; @@ -2950,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; @@ -2979,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; } @@ -3005,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. + * + * @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 $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])) { @@ -3101,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_deprecated_notice('list_registered_entities() was deprecated by elgg_list_registered_entities().', 1.7); - - $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; } /** @@ -3181,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 "; + $count = 0; + $entities = array(); } - foreach ($where as $w) { - $query .= " $w and "; - } - - // 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; +function elgg_instanceof($entity, $type = NULL, $subtype = NULL, $class = NULL) { + $return = ($entity instanceof ElggEntity); - $entity_guid = (int) $entity_guid; - $name = sanitise_string($name); - $value = sanitise_string($value); - - $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; @@ -3657,33 +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'); + 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'); +elgg_register_event_handler('init', 'system', 'entities_init'); + diff --git a/engine/lib/exceptions.php b/engine/lib/exceptions.php deleted file mode 100644 index ccf017062..000000000 --- a/engine/lib/exceptions.php +++ /dev/null @@ -1,161 +0,0 @@ -<?php -/** - * Exceptions. - * Define some globally useful exception classes. - * - * @package Elgg - * @subpackage Exceptions - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ - */ - -// Top level ////////////////////////////////////////////////////////////////////////////// - -/** - * IOException - * An IO Exception, throw when an IO Exception occurs. Subclass for specific IO Exceptions. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class IOException extends Exception {} - -/** - * ClassException - * A class Exception, throw when there is a class error. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class ClassException extends Exception {} - -/** - * ConfigurationException - * There is a configuration error - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class ConfigurationException extends Exception {} - -/** - * SecurityException - * An Security Exception, throw when a Security Exception occurs. Subclass for specific Security Execeptions (access problems etc) - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class SecurityException extends Exception {} - -/** - * ClassNotFoundException - * An database exception, throw when a database exception happens, subclass if more detail is needed. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class DatabaseException extends Exception {} - -/** - * APIException - * The API Exception class, thrown by the API layer when an API call has an issue. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class APIException extends Exception {} - -/** - * CallException - * An exception thrown when there is a problem calling something. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class CallException extends Exception {} - -/** - * Data format exception - * An exception thrown when there is a problem in the format of some data. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class DataFormatException extends Exception {} - -// Class exceptions /////////////////////////////////////////////////////////////////////// - -/** - * InvalidClassException - * An invalid class Exception, throw when a class is invalid. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class InvalidClassException extends ClassException {} - -/** - * ClassNotFoundException - * An Class not found Exception, throw when an class can not be found occurs. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class ClassNotFoundException extends ClassException {} - -// Configuration exceptions /////////////////////////////////////////////////////////////// - -/** - * InstallationException - * Thrown when there is a major problem with the installation. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class InstallationException extends ConfigurationException {} - -// Call exceptions //////////////////////////////////////////////////////////////////////// - -/** - * NotImplementedException - * Thrown when a method or function has not been implemented, primarily used in development... you should - * not see these! - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class NotImplementedException extends CallException {} - -/** - * InvalidParameterException - * A parameter is invalid. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class InvalidParameterException extends CallException {} - -// Installation exception ///////////////////////////////////////////////////////////////// - -/** - * RegistrationException - * Could not register a new user for whatever reason. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Exceptions - */ -class RegistrationException extends InstallationException {}
\ No newline at end of file diff --git a/engine/lib/export.php b/engine/lib/export.php index c541b583b..ecc894e63 100644 --- a/engine/lib/export.php +++ b/engine/lib/export.php @@ -2,71 +2,16 @@ /** * Elgg Data import export functionality. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage DataModel.Export */ /** - * Define an interface for all ODD exportable objects. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - */ -interface Exportable { - /** - * This must take the contents of the object and convert it to exportable ODD - * @return object or array of objects. - */ - public function export(); - - /** - * Return a list of all fields that can be exported. - * This should be used as the basis for the values returned by export() - */ - public function getExportableValues(); -} - -/** - * Define an interface for all ODD importable objects. - * @author Curverider Ltd - */ -interface Importable { - /** - * Accepts an array of data to import, this data is parsed from the XML produced by export. - * The function should return the constructed object data, or NULL. - * - * @param ODD $data - * @return bool - * @throws ImportException if there was a critical error importing data. - */ - public function import(ODD $data); -} - -/** - * Export exception - * - * @package Elgg - * @subpackage Exceptions - * - */ -class ExportException extends DataFormatException {} - -/** - * Import exception - * - * @package Elgg - * @subpackage Exceptions - */ -class ImportException extends DataFormatException {} - -/** * Get a UUID from a given object. * - * @param $object The object either an ElggEntity, ElggRelationship or ElggExtender - * @return the UUID or false + * @param mixed $object The object either an ElggEntity, ElggRelationship or ElggExtender + * + * @return string|false the UUID or false */ function get_uuid_from_object($object) { if ($object instanceof ElggEntity) { @@ -74,9 +19,9 @@ function get_uuid_from_object($object) { } else if ($object instanceof ElggExtender) { $type = $object->type; if ($type == 'volatile') { - $uuid = guid_to_uuid($object->entity_guid). $type . "/{$object->name}/"; + $uuid = guid_to_uuid($object->entity_guid) . $type . "/{$object->name}/"; } else { - $uuid = guid_to_uuid($object->entity_guid). $type . "/{$object->id}/"; + $uuid = guid_to_uuid($object->entity_guid) . $type . "/{$object->id}/"; } return $uuid; @@ -91,22 +36,22 @@ function get_uuid_from_object($object) { * Generate a UUID from a given GUID. * * @param int $guid The GUID of an object. + * + * @return string */ function guid_to_uuid($guid) { - global $CONFIG; - - return $CONFIG->wwwroot . "export/opendd/$guid/"; + return elgg_get_site_url() . "export/opendd/$guid/"; } /** * Test to see if a given uuid is for this domain, returning true if so. - * @param $uuid + * + * @param string $uuid A unique ID + * * @return bool */ function is_uuid_this_domain($uuid) { - global $CONFIG; - - if (strpos($uuid, $CONFIG->wwwroot) === 0) { + if (strpos($uuid, elgg_get_site_url()) === 0) { return true; } @@ -116,12 +61,15 @@ function is_uuid_this_domain($uuid) { /** * This function attempts to retrieve a previously imported entity via its UUID. * - * @param $uuid + * @param string $uuid A unique ID + * + * @return ElggEntity|false */ function get_entity_from_uuid($uuid) { $uuid = sanitise_string($uuid); - $entities = elgg_get_entities_from_metadata(array('metadata_name' => 'import_uuid', 'metadata_value' => $uuid)); + $options = array('metadata_name' => 'import_uuid', 'metadata_value' => $uuid); + $entities = elgg_get_entities_from_metadata($options); if ($entities) { return $entities[0]; @@ -133,14 +81,17 @@ function get_entity_from_uuid($uuid) { /** * Tag a previously created guid with the uuid it was imported on. * - * @param int $guid - * @param string $uuid + * @param int $guid A GUID + * @param string $uuid A Unique ID + * + * @return bool */ function add_uuid_to_guid($guid, $uuid) { $guid = (int)$guid; $uuid = sanitise_string($uuid); - return create_metadata($guid, "import_uuid", $uuid); + $result = create_metadata($guid, "import_uuid", $uuid); + return (bool)$result; } @@ -154,40 +105,50 @@ $IMPORTED_OBJECT_COUNTER = 0; * If nobody processes the top level element, the sub level elements are processed. * * @param ODD $odd The odd element to process + * + * @return bool + * @access private */ -function __process_element(ODD $odd) { +function _process_element(ODD $odd) { global $IMPORTED_DATA, $IMPORTED_OBJECT_COUNTER; // See if anyone handles this element, return true if it is. + $to_be_serialised = null; if ($odd) { - $handled = trigger_plugin_hook("import", "all", array("element" => $odd), $to_be_serialised); - } + $handled = elgg_trigger_plugin_hook("import", "all", array("element" => $odd), $to_be_serialised); - // If not, then see if any of its sub elements are handled - if ($handled) { - // Increment validation counter - $IMPORTED_OBJECT_COUNTER ++; - // Return the constructed object - $IMPORTED_DATA[] = $handled; + // If not, then see if any of its sub elements are handled + if ($handled) { + // Increment validation counter + $IMPORTED_OBJECT_COUNTER ++; + // Return the constructed object + $IMPORTED_DATA[] = $handled; - return true; + return true; + } } return false; } +/** + * Exports an entity as an array + * + * @param int $guid Entity GUID + * + * @return array + * @throws ExportException + * @access private + */ function exportAsArray($guid) { $guid = (int)$guid; - // Initialise the array - $to_be_serialised = array(); - // Trigger a hook to - $to_be_serialised = trigger_plugin_hook("export", "all", array("guid" => $guid), $to_be_serialised); + $to_be_serialised = elgg_trigger_plugin_hook("export", "all", array("guid" => $guid), array()); // Sanity check - if ((!is_array($to_be_serialised)) || (count($to_be_serialised)==0)) { - throw new ExportException(sprintf(elgg_echo('ExportException:NoSuchEntity'), $guid)); + if ((!is_array($to_be_serialised)) || (count($to_be_serialised) == 0)) { + throw new ExportException(elgg_echo('ExportException:NoSuchEntity', array($guid))); } return $to_be_serialised; @@ -201,10 +162,11 @@ function exportAsArray($guid) { * This function makes use of the "serialise" plugin hook, which is passed an array to which plugins * should add data to be serialised to. * - * @see ElggEntity for an example of its usage. * @param int $guid The GUID. - * @param ODDWrapperFactory $wrapper Optional wrapper permitting the export process to embed ODD in other document formats. - * @return xml + * + * @return string XML + * @see ElggEntity for an example of its usage. + * @access private */ function export($guid) { $odd = new ODDDocument(exportAsArray($guid)); @@ -216,9 +178,11 @@ function export($guid) { * Import an XML serialisation of an object. * This will make a best attempt at importing a given xml doc. * - * @param string $xml + * @param string $xml XML string + * * @return bool - * @throws Exception if there was a problem importing the data. + * @throws ImportException if there was a problem importing the data. + * @access private */ function import($xml) { global $IMPORTED_DATA, $IMPORTED_OBJECT_COUNTER; @@ -232,10 +196,10 @@ function import($xml) { } foreach ($document as $element) { - __process_element($element); + _process_element($element); } - if ($IMPORTED_OBJECT_COUNTER!= count($IMPORTED_DATA)) { + if ($IMPORTED_OBJECT_COUNTER != count($IMPORTED_DATA)) { throw new ImportException(elgg_echo('ImportException:NotAllImported')); } @@ -245,12 +209,15 @@ function import($xml) { /** * Register the OpenDD import action + * + * @return void + * @access private */ function export_init() { global $CONFIG; - register_action("import/opendd", false); + elgg_register_action("import/opendd"); } // Register a startup event -register_elgg_event_handler('init', 'system', 'export_init', 100); +elgg_register_event_handler('init', 'system', 'export_init', 100); diff --git a/engine/lib/extender.php b/engine/lib/extender.php index cbcdd6eb7..8323bd3ce 100644 --- a/engine/lib/extender.php +++ b/engine/lib/extender.php @@ -3,274 +3,23 @@ * Elgg Entity Extender. * This file contains ways of extending an Elgg entity in custom ways. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage DataModel.Extender */ /** - * ElggExtender - * - * @author Curverider Ltd - * @package Elgg - * @subpackage Core - */ -abstract class ElggExtender implements - Exportable, - Loggable, // Can events related to this object class be logged - Iterator, // Override foreach behaviour - ArrayAccess // Override for array access -{ - /** - * This contains the site's main properties (id, etc) - * @var array - */ - protected $attributes; - - /** - * Get an attribute - * - * @param string $name - * @return mixed - */ - protected function get($name) { - if (isset($this->attributes[$name])) { - // Sanitise value if necessary - if ($name=='value') { - switch ($this->attributes['value_type']) { - case 'integer' : - return (int)$this->attributes['value']; - - //case 'tag' : - //case 'file' : - case 'text' : - return ($this->attributes['value']); - - default : - throw new InstallationException(sprintf(elgg_echo('InstallationException:TypeNotSupported'), $this->attributes['value_type'])); - } - } - - return $this->attributes[$name]; - } - return null; - } - - /** - * Set an attribute - * - * @param string $name - * @param mixed $value - * @param string $value_type - * @return boolean - */ - protected function set($name, $value, $value_type = "") { - $this->attributes[$name] = $value; - if ($name == 'value') { - $this->attributes['value_type'] = detect_extender_valuetype($value, $value_type); - } - - return true; - } - - /** - * Return the owner of this annotation. - * - * @return mixed - */ - public function getOwner() { - return $this->owner_guid; - } - - /** - * Return the owner entity - * - * @return mixed - */ - public function getOwnerEntity() { - return get_user($this->owner_guid); - } - - /** - * Returns the entity this is attached to - * - * @return ElggEntity The enttiy - */ - public function getEntity() { - return get_entity($this->entity_guid); - } - - /** - * Save this data to the appropriate database table. - */ - abstract public function save(); - - /** - * Delete this data. - */ - abstract public function delete(); - - /** - * Determines whether or not the specified user can edit this - * - * @param int $user_guid The GUID of the user (defaults to currently logged in user) - * @return true|false - */ - public function canEdit($user_guid = 0) { - return can_edit_extender($this->id,$this->type,$user_guid); - } - - /** - * Return a url for this extender. - * - * @return string - */ - public abstract function getURL(); - - // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// - - /** - * Return an array of fields which can be exported. - */ - public function getExportableValues() { - return array( - 'id', - 'entity_guid', - 'name', - 'value', - 'value_type', - 'owner_guid', - 'type', - ); - } - - /** - * Export this object - * - * @return array - */ - public function export() { - $uuid = get_uuid_from_object($this); - - $meta = new ODDMetadata($uuid, guid_to_uuid($this->entity_guid), $this->attributes['name'], $this->attributes['value'], $this->attributes['type'], guid_to_uuid($this->owner_guid)); - $meta->setAttribute('published', date("r", $this->time_created)); - - return $meta; - } - - // 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->id; - } - - /** - * Return the class name of the object. - */ - public function getClassName() { - return get_class($this); - } - - /** - * Return the GUID of the owner of this object. - */ - public function getObjectOwnerGUID() { - return $this->owner_guid; - } - - /** - * Return a type of the object - eg. object, group, user, relationship, metadata, annotation etc - */ - public function getType() { - return $this->type; - } - - /** - * Return a subtype. For metadata & annotations this is the 'name' and - * for relationship this is the relationship type. - */ - public function getSubtype() { - return $this->name; - } - - - // ITERATOR INTERFACE ////////////////////////////////////////////////////////////// - /* - * This lets an entity's attributes be displayed using foreach as a normal array. - * Example: http://www.sitepoint.com/print/php5-standard-library - */ - - private $valid = FALSE; - - function rewind() { - $this->valid = (FALSE !== reset($this->attributes)); - } - - function current() { - return current($this->attributes); - } - - function key() { - return key($this->attributes); - } - - function next() { - $this->valid = (FALSE !== next($this->attributes)); - } - - function valid() { - return $this->valid; - } - - // ARRAY ACCESS INTERFACE ////////////////////////////////////////////////////////// - /* - * This lets an entity's attributes be accessed like an associative array. - * Example: http://www.sitepoint.com/print/php5-standard-library - */ - - function offsetSet($key, $value) { - if ( array_key_exists($key, $this->attributes) ) { - $this->attributes[$key] = $value; - } - } - - function offsetGet($key) { - if ( array_key_exists($key, $this->attributes) ) { - return $this->attributes[$key]; - } - } - - function offsetUnset($key) { - if ( array_key_exists($key, $this->attributes) ) { - // Full unsetting is dangerious for our objects - $this->attributes[$key] = ""; - } - } - - function offsetExists($offset) { - return array_key_exists($offset, $this->attributes); - } -} - -/** * Detect the value_type for a given value. * Currently this is very crude. * - * TODO: Make better! + * @todo Make better! * - * @param mixed $value + * @param mixed $value The value * @param string $value_type If specified, overrides the detection. + * * @return string */ function detect_extender_valuetype($value, $value_type = "") { - if ($value_type!="") { + if ($value_type != "" && ($value_type == 'integer' || $value_type == 'text')) { return $value_type; } @@ -287,12 +36,15 @@ function detect_extender_valuetype($value, $value_type = "") { } /** - * Utility function used by import_extender_plugin_hook() to process an ODDMetaData and add it to an entity. - * This function does not hit ->save() on the entity (this lets you construct in memory) + * Utility function used by import_extender_plugin_hook() to process + * an ODDMetaData and add it to an entity. This function does not + * hit ->save() on the entity (this lets you construct in memory) * - * @param ElggEntity The entity to add the data to. + * @param ElggEntity $entity The entity to add the data to. * @param ODDMetaData $element The OpenDD element + * * @return bool + * @access private */ function oddmetadata_to_elggextender(ElggEntity $entity, ODDMetaData $element) { // Get the type of extender (metadata, type, attribute etc) @@ -325,6 +77,17 @@ function oddmetadata_to_elggextender(ElggEntity $entity, ODDMetaData $element) { /** * Handler called by trigger_plugin_hook on the "import" event. + * + * @param string $hook volatile + * @param string $entity_type metadata + * @param string $returnvalue Return value from previous hook + * @param array $params The parameters + * + * @return null + * @elgg_plugin_hook_handler volatile metadata + * @todo investigate more. + * @throws ImportException + * @access private */ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) { $element = $params['element']; @@ -332,18 +95,21 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) $tmp = NULL; if ($element instanceof ODDMetaData) { + /* @var ODDMetaData $element */ // Recall entity $entity_uuid = $element->getAttribute('entity_uuid'); $entity = get_entity_from_uuid($entity_uuid); if (!$entity) { - throw new ImportException(sprintf(elgg_echo('ImportException:GUIDNotFound'), $entity_uuid)); + throw new ImportException(elgg_echo('ImportException:GUIDNotFound', array($entity_uuid))); } oddmetadata_to_elggextender($entity, $element); // Save if (!$entity->save()) { - throw new ImportException(sprintf(elgg_echo('ImportException:ProblemUpdatingMeta'), $attr_name, $entity_uuid)); + $attr_name = $element->getAttribute('name'); + $msg = elgg_echo('ImportException:ProblemUpdatingMeta', array($attr_name, $entity_uuid)); + throw new ImportException($msg); } return true; @@ -353,61 +119,72 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) /** * Determines whether or not the specified user can edit the specified piece of extender * - * @param int $extender_id The ID of the piece of extender - * @param string $type 'metadata' or 'annotation' - * @param int $user_guid The GUID of the user - * @return true|false + * @param int $extender_id The ID of the piece of extender + * @param string $type 'metadata' or 'annotation' + * @param int $user_guid The GUID of the user + * + * @return bool */ function can_edit_extender($extender_id, $type, $user_guid = 0) { - if (!isloggedin()) { - return false; + // @todo Since Elgg 1.0, Elgg has returned false from can_edit_extender() + // if no user was logged in. This breaks the access override. This is a + // temporary work around. This function needs to be rewritten in Elgg 1.9 + if (!elgg_check_access_overrides($user_guid)) { + if (!elgg_is_logged_in()) { + return false; + } } $user_guid = (int)$user_guid; - $user = get_entity($user_guid); + $user = get_user($user_guid); if (!$user) { - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); + $user_guid = elgg_get_logged_in_user_guid(); } - $functionname = "get_{$type}"; + $functionname = "elgg_get_{$type}_from_id"; if (is_callable($functionname)) { - $extender = $functionname($extender_id); + $extender = call_user_func($functionname, $extender_id); } else { return false; } - if (!is_a($extender,"ElggExtender")) { + if (!($extender instanceof ElggExtender)) { return false; } + /* @var ElggExtender $extender */ // If the owner is the specified user, great! They can edit. - if ($extender->getOwner() == $user->getGUID()) { + if ($extender->getOwnerGUID() == $user_guid) { return true; } // If the user can edit the entity this is attached to, great! They can edit. - if (can_edit_entity($extender->entity_guid,$user->getGUID())) { + if (can_edit_entity($extender->entity_guid, $user_guid)) { return true; } - // Trigger plugin hooks - return trigger_plugin_hook('permissions_check',$type,array('entity' => $entity, 'user' => $user),false); + // Trigger plugin hook - note that $user may be null + $params = array('entity' => $extender->getEntity(), 'user' => $user); + return elgg_trigger_plugin_hook('permissions_check', $type, $params, false); } /** * Sets the URL handler for a particular extender type and name. - * It is recommended that you do not call this directly, instead use one of the wrapper functions in the - * subtype files. + * It is recommended that you do not call this directly, instead use + * one of the wrapper functions such as elgg_register_annotation_url_handler(). * - * @param string $function_name The function to register - * @param string $extender_type Extender type + * @param string $extender_type Extender type ('annotation', 'metadata') * @param string $extender_name The name of the extender - * @return true|false Depending on success + * @param string $function_name The function to register + * + * @return bool */ -function register_extender_url_handler($function_name, $extender_type = "all", $extender_name = "all") { +function elgg_register_extender_url_handler($extender_type, $extender_name, $function_name) { + global $CONFIG; - if (!is_callable($function_name)) { + if (!is_callable($function_name, true)) { return false; } @@ -426,7 +203,9 @@ function register_extender_url_handler($function_name, $extender_type = "all", $ * Get the URL of a given elgg extender. * Used by get_annotation_url and get_metadata_url. * - * @param ElggExtender $extender + * @param ElggExtender $extender An extender object + * + * @return string */ function get_extender_url(ElggExtender $extender) { global $CONFIG; @@ -452,18 +231,19 @@ function get_extender_url(ElggExtender $extender) { } if (is_callable($function)) { - $url = $function($extender); + $url = call_user_func($function, $extender); } if ($url == "") { $nameid = $extender->id; if ($type == 'volatile') { - $nameid== $extender->name; + $nameid = $extender->name; } - $url = $CONFIG->wwwroot . "export/$view/$guid/$type/$nameid/"; + $url = "export/$view/$guid/$type/$nameid/"; } - return $url; + + return elgg_normalize_url($url); } /** Register the hook */ -register_plugin_hook("import", "all", "import_extender_plugin_hook", 2);
\ No newline at end of file +elgg_register_plugin_hook_handler("import", "all", "import_extender_plugin_hook", 2); diff --git a/engine/lib/filestore.php b/engine/lib/filestore.php index d131154c6..a3c7ba439 100644 --- a/engine/lib/filestore.php +++ b/engine/lib/filestore.php @@ -1,723 +1,30 @@ <?php /** * Elgg filestore. - * This file contains classes, interfaces and functions for saving and retrieving data to various file - * stores. + * This file contains classes, interfaces and functions for + * saving and retrieving data to various file stores. * - * @package Elgg - * @subpackage API - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage DataModel.FileStorage */ -include_once("objects.php"); - -/** - * @class ElggFilestore - * This class defines the interface for all elgg data repositories. - * @author Curverider Ltd - */ -abstract class ElggFilestore { - /** - * Attempt to open the file $file for storage or writing. - * - * @param ElggFile $file - * @param string $mode "read", "write", "append" - * @return mixed A handle to the opened file or false on error. - */ - abstract public function open(ElggFile $file, $mode); - - /** - * Write data to a given file handle. - * - * @param mixed $f The file handle - exactly what this is depends on the file system - * @param string $data The binary string of data to write - * @return int Number of bytes written. - */ - abstract public function write($f, $data); - - /** - * Read data from a filestore. - * - * @param mixed $f The file handle - * @param int $length Length in bytes to read. - * @param int $offset The optional offset. - * @return mixed String of data or false on error. - */ - abstract public function read($f, $length, $offset = 0); - - /** - * Seek a given position within a file handle. - * - * @param mixed $f The file handle. - * @param int $position The position. - */ - abstract public function seek($f, $position); - - /** - * Return a whether the end of a file has been reached. - * - * @param mixed $f The file handle. - * @return boolean - */ - abstract public function eof($f); - - /** - * Return the current position in an open file. - * - * @param mixed $f The file handle. - * @return int - */ - abstract public function tell($f); - - /** - * Close a given file handle. - * - * @param mixed $f - */ - abstract public function close($f); - - /** - * Delete the file associated with a given file handle. - * - * @param ElggFile $file - */ - abstract public function delete(ElggFile $file); - - /** - * Return the size in bytes for a given file. - * - * @param ElggFile $file - */ - abstract public function getFileSize(ElggFile $file); - - /** - * Return the filename of a given file as stored on the filestore. - * - * @param ElggFile $file - */ - abstract public function getFilenameOnFilestore(ElggFile $file); - - /** - * Get the filestore's creation parameters as an associative array. - * Used for serialisation and for storing the creation details along side a file object. - * - * @return array - */ - abstract public function getParameters(); - - /** - * Set the parameters from the associative array produced by $this->getParameters(). - */ - abstract public function setParameters(array $parameters); - - /** - * Get the contents of the whole file. - * - * @param mixed $file The file handle. - * @return mixed The file contents. - */ - abstract public function grabFile(ElggFile $file); - - /** - * Return whether a file physically exists or not. - * - * @param ElggFile $file - */ - abstract public function exists(ElggFile $file); -} - -/** - * @class ElggDiskFilestore - * This class uses disk storage to save data. - * @author Curverider Ltd - */ -class ElggDiskFilestore extends ElggFilestore { - /** - * Directory root. - */ - private $dir_root; - - /** - * Default depth of file directory matrix - */ - private $matrix_depth = 5; - - /** - * Construct a disk filestore using the given directory root. - * - * @param string $directory_root Root directory, must end in "/" - */ - public function __construct($directory_root = "") { - global $CONFIG; - - if ($directory_root) { - $this->dir_root = $directory_root; - } else { - $this->dir_root = $CONFIG->dataroot; - } - } - - public function open(ElggFile $file, $mode) { - $fullname = $this->getFilenameOnFilestore($file); - - // Split into path and name - $ls = strrpos($fullname,"/"); - if ($ls===false) { - $ls = 0; - } - - $path = substr($fullname, 0, $ls); - $name = substr($fullname, $ls); - - // Try and create the directory - try { - $this->make_directory_root($path); - } catch (Exception $e) { - - } - - if (($mode!='write') && (!file_exists($fullname))) { - return false; - } - - switch ($mode) { - case "read" : - $mode = "rb"; - break; - case "write" : - $mode = "w+b"; - break; - case "append" : - $mode = "a+b"; - break; - default: - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:UnrecognisedFileMode'), $mode)); - } - - return fopen($fullname, $mode); - - } - - public function write($f, $data) { - return fwrite($f, $data); - } - - public function read($f, $length, $offset = 0) { - if ($offset) { - $this->seek($f, $offset); - } - - return fread($f, $length); - } - - public function close($f) { - return fclose($f); - } - - public function delete(ElggFile $file) { - $filename = $this->getFilenameOnFilestore($file); - if (file_exists($filename)) { - return unlink($filename); - } else { - return true; - } - } - - public function seek($f, $position) { - return fseek($f, $position); - } - - public function tell($f) { - return ftell($f); - } - - public function eof($f) { - return feof($f); - } - - public function getFileSize(ElggFile $file) { - return filesize($this->getFilenameOnFilestore($file)); - } - - public function getFilenameOnFilestore(ElggFile $file) { - $owner = $file->getOwnerEntity(); - if (!$owner) { - $owner = get_loggedin_user(); - } - - if ((!$owner) || (!$owner->username)) { - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:MissingOwner'), $file->getFilename(), $file->guid)); - } - - return $this->dir_root . $this->make_file_matrix($owner->guid) . $file->getFilename(); - } - - public function grabFile(ElggFile $file) { - return file_get_contents($file->getFilenameOnFilestore()); - } - - public function exists(ElggFile $file) { - return file_exists($this->getFilenameOnFilestore($file)); - } - - public function getSize($prefix,$container_guid) { - if ($container_guid) { - return get_dir_size($this->dir_root.$this->make_file_matrix($container_guid).$prefix); - } else { - return false; - } - } - - /** - * Make the directory root. - * - * @param string $dirroot - */ - protected function make_directory_root($dirroot) { - if (!file_exists($dirroot)) { - if (!@mkdir($dirroot, 0700, true)) { - throw new IOException(sprintf(elgg_echo('IOException:CouldNotMake'), $dirroot)); - } - } - - return true; - } - - /** - * Multibyte string tokeniser. - * - * Splits a string into an array. Will fail safely if mbstring is not installed (although this may still - * not handle . - * - * @param string $string String - * @param string $charset The charset, defaults to UTF8 - * @return array - */ - private function mb_str_split($string, $charset = 'UTF8') { - if (is_callable('mb_substr')) { - $length = mb_strlen($string); - $array = array(); - - while ($length) { - $array[] = mb_substr($string, 0, 1, $charset); - $string = mb_substr($string, 1, $length, $charset); - - $length = mb_strlen($string); - } - - return $array; - } else { - return str_split($string); - } - - return false; - } - - /** - * Construct the filename matrix. - * - * @param int | string $identifier - * @return str - */ - protected function make_file_matrix($identifier) { - if (is_numeric($identifier)) { - return $this->user_file_matrix($identifier); - } - - return $this->deprecated_file_matrix($identifier); - } - - /** - * Construct the filename matrix with user info - * - * This method will generate a matrix using the entity's creation time and - * unique guid. This is intended only to determine a user's data directory. - * - * @param int $guid - * @return str - */ - protected function user_file_matrix($guid) { - // lookup the entity - $user = get_entity($guid); - if ($user->type != 'user') - { - // only to be used for user directories - return FALSE; - } - - if (!$user->time_created) { - // fall back to deprecated method - return $this->deprecated_file_matrix($user->username); - } - - $time_created = date('Y/m/d', $user->time_created); - return "$time_created/$user->guid/"; - } - - /** - * Construct the filename matrix using a string - * - * Particularly, this is used with a username to generate the file storage - * location. - * - * @deprecated for user directories: use user_file_matrix() instead. - * - * @param str $filename - * @return str - */ - protected function deprecated_file_matrix($filename) { - // throw a warning for using deprecated method - $error = 'Deprecated use of ElggDiskFilestore::make_file_matrix. '; - $error .= 'Username passed instead of guid.'; - elgg_log($error, WARNING); - - $user = new ElggUser($filename); - return $this->user_file_matrix($user->guid); - } - - public function getParameters() { - return array("dir_root" => $this->dir_root); - } - - public function setParameters(array $parameters) { - if (isset($parameters['dir_root'])) { - $this->dir_root = $parameters['dir_root']; - return true; - } - - return false; - } -} - -/** - * @class ElggFile - * This class represents a physical file. - * - * Usage: - * Create a new ElggFile object and specify a filename, and optionally a FileStore (if one isn't specified - * then the default is assumed. - * - * Open the file using the appropriate mode, and you will be able to read and write to the file. - * - * Optionally, you can also call the file's save() method, this will turn the file into an entity in the - * system and permit you to do things like attach tags to the file etc. This is not done automatically since - * there are many occasions where you may want access to file data on datastores using the ElggFile interface - * but do not want to create an Entity reference to it in the system (temporary files for example). - * - * @author Curverider Ltd - */ -class ElggFile extends ElggObject { - /** Filestore */ - private $filestore; - - /** File handle used to identify this file in a filestore. Created by open. */ - private $handle; - - protected function initialise_attributes() { - parent::initialise_attributes(); - - $this->attributes['subtype'] = "file"; - } - - public function __construct($guid = null) { - parent::__construct($guid); - - // Set default filestore - $this->filestore = $this->getFilestore(); - } - - /** - * Set the filename of this file. - * - * @param string $name The filename. - */ - public function setFilename($name) { - $this->filename = $name; - } - - /** - * Return the filename. - */ - public function getFilename() { - return $this->filename; - } - - /** - * Return the filename of this file as it is/will be stored on the filestore, which may be different - * to the filename. - */ - public function getFilenameOnFilestore() { - return $this->filestore->getFilenameOnFilestore($this); - } - - /* - * Return the size of the filestore associated with this file - * - */ - public function getFilestoreSize($prefix='',$container_guid=0) { - if (!$container_guid) { - $container_guid = $this->container_guid; - } - $fs = $this->getFilestore(); - return $fs->getSize($prefix,$container_guid); - } - - /** - * Get the mime type of the file. - */ - public function getMimeType() { - if ($this->mimetype) { - return $this->mimetype; - } - - // TODO : Guess mimetype if not here - } - - /** - * Set the mime type of the file. - * - * @param $mimetype The mimetype - */ - public function setMimeType($mimetype) { - return $this->mimetype = $mimetype; - } - - /** - * Set the optional file description. - * - * @param string $description The description. - */ - public function setDescription($description) { - $this->description = $description; - } - - /** - * Open the file with the given mode - * - * @param string $mode Either read/write/append - */ - public function open($mode) { - if (!$this->getFilename()) { - throw new IOException(elgg_echo('IOException:MissingFileName')); - } - - // See if file has already been saved - // seek on datastore, parameters and name? - - // Sanity check - if ( - ($mode!="read") && - ($mode!="write") && - ($mode!="append") - ) { - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:UnrecognisedFileMode'), $mode)); - } - - // Get the filestore - $fs = $this->getFilestore(); - - // Ensure that we save the file details to object store - //$this->save(); - - // Open the file handle - $this->handle = $fs->open($this, $mode); - - return $this->handle; - } - - /** - * Write some data. - * - * @param string $data The data - */ - public function write($data) { - $fs = $this->getFilestore(); - - return $fs->write($this->handle, $data); - } - - /** - * Read some data. - * - * @param int $length Amount to read. - * @param int $offset The offset to start from. - */ - public function read($length, $offset = 0) { - $fs = $this->getFilestore(); - - return $fs->read($this->handle, $length, $offset); - } - - /** - * Gets the full contents of this file. - * - * @return mixed The file contents. - */ - public function grabFile() { - $fs = $this->getFilestore(); - return $fs->grabFile($this); - } - - /** - * Close the file and commit changes - */ - public function close() { - $fs = $this->getFilestore(); - - if ($fs->close($this->handle)) { - $this->handle = NULL; - - return true; - } - - return false; - } - - /** - * Delete this file. - */ - public function delete() { - $fs = $this->getFilestore(); - if ($fs->delete($this)) { - return parent::delete(); - } - } - - /** - * Seek a position in the file. - * - * @param int $position - */ - public function seek($position) { - $fs = $this->getFilestore(); - - return $fs->seek($this->handle, $position); - } - - /** - * Return the current position of the file. - * - * @return int The file position - */ - public function tell() { - $fs = $this->getFilestore(); - - return $fs->tell($this->handle); - } - - /** - * Return the size of the file in bytes. - */ - public function size() { - return $this->filestore->getFileSize($this); - } - - /** - * Return a boolean value whether the file handle is at the end of the file - */ - public function eof() { - $fs = $this->getFilestore(); - - return $fs->eof($this->handle); - } - - public function exists() { - $fs = $this->getFilestore(); - - return $fs->exists($this); - } - - /** - * Set a filestore. - * - * @param ElggFilestore $filestore The file store. - */ - public function setFilestore(ElggFilestore $filestore) { - $this->filestore = $filestore; - } - - /** - * Return a filestore suitable for saving this file. - * This filestore is either a pre-registered filestore, a filestore loaded from metatags saved - * along side this file, or the system default. - */ - protected function getFilestore() { - // Short circuit if already set. - if ($this->filestore) { - return $this->filestore; - } - - // If filestore meta set then retrieve filestore TODO: Better way of doing this? - $metas = get_metadata_for_entity($this->guid); - $parameters = array(); - if (is_array($metas)) { - foreach ($metas as $meta) { - if (strpos($meta->name, "filestore::")!==false) { - // Filestore parameter tag - $comp = explode("::", $meta->name); - $name = $comp[1]; - - $parameters[$name] = $meta->value; - } - } - } - - // If parameters loaded then create new filestore - if (count($parameters)!=0) { - // Create new filestore object - if ((!isset($parameters['filestore'])) || (!class_exists($parameters['filestore']))) { - throw new ClassNotFoundException(elgg_echo('ClassNotFoundException:NotFoundNotSavedWithFile')); - } - - $this->filestore = new $parameters['filestore'](); - - // Set parameters - $this->filestore->setParameters($parameters); - } - - - // if still nothing then set filestore to default - if (!$this->filestore) { - $this->filestore = get_default_filestore(); - } - - return $this->filestore; - } - - public function save() { - if (!parent::save()) { - return false; - } - - // Save datastore metadata - $params = $this->filestore->getParameters(); - foreach ($params as $k => $v) { - $this->setMetaData("filestore::$k", $v); - } - - // Now make a note of the filestore class - $this->setMetaData("filestore::filestore", get_class($this->filestore)); - - return true; - } -} - /** * Get the size of the specified directory. * - * @param string $dir The full path of the directory + * @param string $dir The full path of the directory + * @param int $totalsize Add to current dir size + * * @return int The size of the directory. */ -function get_dir_size($dir, $totalsize = 0){ +function get_dir_size($dir, $totalsize = 0) { $handle = @opendir($dir); - while ($file = @readdir ($handle)){ + while ($file = @readdir($handle)) { if (eregi("^\.{1,2}$", $file)) { continue; } - if(is_dir($dir . $file)) { + if (is_dir($dir . $file)) { $totalsize = get_dir_size($dir . $file . "/", $totalsize); - } else{ + } else { $totalsize += filesize($dir . $file); } } @@ -731,6 +38,7 @@ function get_dir_size($dir, $totalsize = 0){ * (Returns false if there was an issue.) * * @param string $input_name The name of the file input field on the submission form + * * @return mixed|false The contents of the file, or false on failure. */ function get_uploaded_file($input_name) { @@ -746,15 +54,22 @@ function get_uploaded_file($input_name) { * (Returns false if the uploaded file was not an image) * * @param string $input_name The name of the file input field on the submission form - * @param int $maxwidth The maximum width of the resized image - * @param int $maxheight The maximum height of the resized image - * @param true|false $square If set to true, will take the smallest of maxwidth and maxheight and use it to set the dimensions on all size; the image will be cropped. + * @param int $maxwidth The maximum width of the resized image + * @param int $maxheight The maximum height of the resized image + * @param bool $square If set to true, will take the smallest + * of maxwidth and maxheight and use it to set the + * dimensions on all size; the image will be cropped. + * @param bool $upscale Resize images smaller than $maxwidth x $maxheight? + * * @return false|mixed The contents of the resized image, or false on failure */ -function get_resized_image_from_uploaded_file($input_name, $maxwidth, $maxheight, $square = false) { +function get_resized_image_from_uploaded_file($input_name, $maxwidth, $maxheight, +$square = false, $upscale = false) { + // If our file exists ... if (isset($_FILES[$input_name]) && $_FILES[$input_name]['error'] == 0) { - return get_resized_image_from_existing_file($_FILES[$input_name]['tmp_name'], $maxwidth, $maxheight, $square); + return get_resized_image_from_existing_file($_FILES[$input_name]['tmp_name'], $maxwidth, + $maxheight, $square, 0, 0, 0, 0, $upscale); } return false; @@ -765,32 +80,33 @@ function get_resized_image_from_uploaded_file($input_name, $maxwidth, $maxheight * (Returns false if the file was not an image) * * @param string $input_name The name of the file on the disk - * @param int $maxwidth The desired width of the resized image - * @param int $maxheight The desired height of the resized image - * @param true|false $square If set to true, takes the smallest of maxwidth and - * maxheight and use it to set the dimensions on the new image. If no - * crop parameters are set, the largest square that fits in the image - * centered will be used for the resize. If square, the crop must be a - * square region. - * @param int $x1 x coordinate for top, left corner - * @param int $y1 y coordinate for top, left corner - * @param int $x2 x coordinate for bottom, right corner - * @param int $y2 y coordinate for bottom, right corner - * @param bool $upscale Resize images smaller than $maxwidth x $maxheight? + * @param int $maxwidth The desired width of the resized image + * @param int $maxheight The desired height of the resized image + * @param bool $square If set to true, takes the smallest of maxwidth and + * maxheight and use it to set the dimensions on the new image. + * If no crop parameters are set, the largest square that fits + * in the image centered will be used for the resize. If square, + * the crop must be a square region. + * @param int $x1 x coordinate for top, left corner + * @param int $y1 y coordinate for top, left corner + * @param int $x2 x coordinate for bottom, right corner + * @param int $y2 y coordinate for bottom, right corner + * @param bool $upscale Resize images smaller than $maxwidth x $maxheight? + * * @return false|mixed The contents of the resized image, or false on failure */ -function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight, $square = FALSE, $x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $upscale = FALSE) { +function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight, $square = FALSE, +$x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $upscale = FALSE) { + // Get the size information from the image $imgsizearray = getimagesize($input_name); if ($imgsizearray == FALSE) { return FALSE; } - // Get width and height $width = $imgsizearray[0]; $height = $imgsizearray[1]; - // make sure we can read the image $accepted_formats = array( 'image/jpeg' => 'jpeg', 'image/pjpeg' => 'jpeg', @@ -805,6 +121,94 @@ function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight return FALSE; } + // get the parameters for resizing the image + $options = array( + 'maxwidth' => $maxwidth, + 'maxheight' => $maxheight, + 'square' => $square, + 'upscale' => $upscale, + 'x1' => $x1, + 'y1' => $y1, + 'x2' => $x2, + 'y2' => $y2, + ); + $params = get_image_resize_parameters($width, $height, $options); + if ($params == FALSE) { + return FALSE; + } + + // load original image + $original_image = $load_function($input_name); + if (!$original_image) { + return FALSE; + } + + // allocate the new image + $new_image = imagecreatetruecolor($params['newwidth'], $params['newheight']); + if (!$new_image) { + return FALSE; + } + + // color transparencies white (default is black) + imagefilledrectangle( + $new_image, 0, 0, $params['newwidth'], $params['newheight'], + imagecolorallocate($new_image, 255, 255, 255) + ); + + $rtn_code = imagecopyresampled( $new_image, + $original_image, + 0, + 0, + $params['xoffset'], + $params['yoffset'], + $params['newwidth'], + $params['newheight'], + $params['selectionwidth'], + $params['selectionheight']); + if (!$rtn_code) { + return FALSE; + } + + // grab a compressed jpeg version of the image + ob_start(); + imagejpeg($new_image, NULL, 90); + $jpeg = ob_get_clean(); + + imagedestroy($new_image); + imagedestroy($original_image); + + return $jpeg; +} + +/** + * Calculate the parameters for resizing an image + * + * @param int $width Width of the original image + * @param int $height Height of the original image + * @param array $options See $defaults for the options + * + * @return array or FALSE + * @since 1.7.2 + */ +function get_image_resize_parameters($width, $height, $options) { + + $defaults = array( + 'maxwidth' => 100, + 'maxheight' => 100, + + 'square' => FALSE, + 'upscale' => FALSE, + + 'x1' => 0, + 'y1' => 0, + 'x2' => 0, + 'y2' => 0, + ); + + $options = array_merge($defaults, $options); + + extract($options); + // crop image first? $crop = TRUE; if ($x1 == 0 && $y1 == 0 && $x2 == 0 && $y2 == 0) { @@ -813,12 +217,12 @@ function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight // how large a section of the image has been selected if ($crop) { - $region_width = $x2 - $x1; - $region_height = $y2 - $y1; + $selection_width = $x2 - $x1; + $selection_height = $y2 - $y1; } else { // everything selected if no crop parameters - $region_width = $width; - $region_height = $height; + $selection_width = $width; + $selection_height = $height; } // determine cropping offsets @@ -826,7 +230,7 @@ function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight // asking for a square image back // detect case where someone is passing crop parameters that are not for a square - if ($crop == TRUE && $region_width != $region_height) { + if ($crop == TRUE && $selection_width != $selection_height) { return FALSE; } @@ -834,7 +238,7 @@ function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight $new_width = $new_height = min($maxwidth, $maxheight); // find largest square that fits within the selected region - $region_width = $region_height = min($region_width, $region_height); + $selection_width = $selection_height = min($selection_width, $selection_height); // set offsets for crop if ($crop) { @@ -844,20 +248,19 @@ function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight $height = $width; } else { // place square region in the center - $widthoffset = floor(($width - $region_width) / 2); - $heightoffset = floor(($height - $region_height) / 2); + $widthoffset = floor(($width - $selection_width) / 2); + $heightoffset = floor(($height - $selection_height) / 2); } } else { // non-square new image - $new_width = $maxwidth; - $new_height = $maxwidth; + $new_height = $maxheight; // maintain aspect ratio of original image/crop - if (($region_height / (float)$new_height) > ($region_width / (float)$new_width)) { - $new_width = floor($new_height * $region_width / (float)$region_height); + if (($selection_height / (float)$new_height) > ($selection_width / (float)$new_width)) { + $new_width = floor($new_height * $selection_width / (float)$selection_height); } else { - $new_height = floor($new_width * $region_height / (float)$region_width); + $new_height = floor($new_width * $selection_height / (float)$selection_width); } // by default, use entire image @@ -870,75 +273,41 @@ function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight } } - // check for upscaling - // @todo This ignores squares, coordinates, and cropping. It's probably not the best idea. - // Size checking should be done in action code, but for backward compatibility - // this duplicates the previous behavior. - if (!$upscale && ($height < $new_height || $width < $new_width)) { - // zero out offsets - $widthoffset = $heightoffset = 0; - - // determine if we can scale it down at all - // (ie, if only one dimension is too small) - // if not, just use original size. - if ($height < $new_height && $width < $new_width) { - $ratio = 1; - } elseif ($height < $new_height) { - $ratio = $new_width / $width; - } elseif ($width < $new_width) { - $ratio = $new_height / $height; + if (!$upscale && ($selection_height < $new_height || $selection_width < $new_width)) { + // we cannot upscale and selected area is too small so we decrease size of returned image + if ($square) { + $new_height = $selection_height; + $new_width = $selection_width; + } else { + if ($selection_height < $new_height && $selection_width < $new_width) { + $new_height = $selection_height; + $new_width = $selection_width; + } } - $region_height = $height; - $region_width = $width; - $new_height = floor($height * $ratio); - $new_width = floor($width * $ratio); } - // load original image - $orig_image = $load_function($input_name); - if (!$orig_image) { - return FALSE; - } - - // allocate the new image - $newimage = imagecreatetruecolor($new_width, $new_height); - if (!$newimage) { - return FALSE; - } - - // create the new image - $rtn_code = imagecopyresampled( $newimage, - $orig_image, - 0, - 0, - $widthoffset, - $heightoffset, - $new_width, - $new_height, - $region_width, - $region_height ); - if (!$rtn_code) { - return FALSE; - } - - // grab contents for return - ob_start(); - imagejpeg($newimage, null, 90); - $jpeg = ob_get_clean(); - - imagedestroy($newimage); - imagedestroy($orig_image); + $params = array( + 'newwidth' => $new_width, + 'newheight' => $new_height, + 'selectionwidth' => $selection_width, + 'selectionheight' => $selection_height, + 'xoffset' => $widthoffset, + 'yoffset' => $heightoffset, + ); - return $jpeg; + return $params; } - -// putting these here for now +/** + * Delete an ElggFile file + * + * @param int $guid ElggFile GUID + * + * @return bool + */ function file_delete($guid) { if ($file = get_entity($guid)) { if ($file->canEdit()) { - $container = get_entity($file->container_guid); - $thumbnail = $file->thumbnail; $smallthumb = $file->smallthumb; $largethumb = $file->largethumb; @@ -972,6 +341,7 @@ function file_delete($guid) { * Returns an overall file type from the mimetype * * @param string $mimetype The MIME type + * * @return string The overall type */ function file_get_general_file_type($mimetype) { @@ -985,397 +355,35 @@ function file_get_general_file_type($mimetype) { break; } - if (substr_count($mimetype,'text/')) { + if (substr_count($mimetype, 'text/')) { return "document"; } - if (substr_count($mimetype,'audio/')) { + if (substr_count($mimetype, 'audio/')) { return "audio"; } - if (substr_count($mimetype,'image/')) { + if (substr_count($mimetype, 'image/')) { return "image"; } - if (substr_count($mimetype,'video/')) { + if (substr_count($mimetype, 'video/')) { return "video"; } - if (substr_count($mimetype,'opendocument')) { + if (substr_count($mimetype, 'opendocument')) { return "document"; } return "general"; } -function file_handle_upload($prefix,$subtype,$plugin) { - $desc = get_input("description"); - $tags = get_input("tags"); - $tags = explode(",", $tags); - $folder = get_input("folder_text"); - if (!$folder) { - $folder = get_input("folder_select"); - } - $access_id = (int) get_input("access_id"); - $container_guid = (int) get_input('container_guid', 0); - if (!$container_guid) { - $container_guid == get_loggedin_userid(); - } - - // Extract file from, save to default filestore (for now) - - // see if a plugin has set a quota for this user - $file_quota = trigger_plugin_hook("$plugin:quotacheck",'user',array('container_guid'=>$container_guid)); - if (!$file_quota) { - // no, see if there is a generic quota set - $file_quota = get_plugin_setting('quota', $plugin); - } - if ($file_quota) { - // convert to megabytes - $file_quota = $file_quota*1000*1024; - } - - // handle uploaded files - $number_of_files = get_input('number_of_files',0); - $quota_exceeded = false; - $bad_mime_type = false; - - for ($i = 0; $i < $number_of_files; $i++) { - $title = get_input("title_".$i); - $uploaded = $_FILES["upload_".$i]; - if (!$uploaded || !$uploaded['name']) { - // no such file, so skip it - continue; - } - if ($plugin == "photo") { - // do a mime type test - if (in_array($uploaded['type'],array('image/jpeg','image/gif','image/png','image/jpg','image/jpe','image/pjpeg','image/x-png'))) { - $file = new PhotoPluginFile(); - } else { - $bad_mime_type = true; - break; - } - } else { - $file = new FilePluginFile(); - } - $dir_size = $file->getFilestoreSize($prefix,$container_guid); - $filestorename = strtolower(time().$uploaded['name']); - $file->setFilename($prefix.$filestorename); - $file->setMimeType($uploaded['type']); - - $file->originalfilename = $uploaded['name']; - - $file->subtype = $subtype; - - $file->access_id = $access_id; - - $uf = get_uploaded_file('upload_'.$i); - - if ($file_quota) { - $file_size = strlen($uf); - if (($dir_size + $file_size) > $file_quota) { - $quota_exceeded = true; - } - } - - if (!$quota_exceeded) { - // all clear, so try to save the data - - $file->open("write"); - $file->write($uf); - $file->close(); - - $file->title = $title; - $file->description = $desc; - if ($container_guid) { - $file->container_guid = $container_guid; - } - - // Save tags - $file->tags = $tags; - - $file->simpletype = file_get_general_file_type($uploaded['type']); - $file->folder = $folder; - - $result = $file->save(); - - if ($result) { - - // Generate thumbnail (if image) - if (substr_count($file->getMimeType(),'image/')) { - $thumbnail = get_resized_image_from_existing_file($file->getFilenameOnFilestore(),60,60, true); - $thumbsmall = get_resized_image_from_existing_file($file->getFilenameOnFilestore(),153,153, true); - $thumblarge = get_resized_image_from_existing_file($file->getFilenameOnFilestore(),600,600, false); - if ($thumbnail) { - $thumb = new ElggFile(); - $thumb->setMimeType($uploaded['type']); - - $thumb->setFilename($prefix."thumb".$filestorename); - $thumb->open("write"); - $thumb->write($thumbnail); - $thumb->close(); - - $file->thumbnail = $prefix."thumb".$filestorename; - - $thumb->setFilename($prefix."smallthumb".$filestorename); - $thumb->open("write"); - $thumb->write($thumbsmall); - $thumb->close(); - $file->smallthumb = $prefix."smallthumb".$filestorename; - - $thumb->setFilename($prefix."largethumb".$filestorename); - $thumb->open("write"); - $thumb->write($thumblarge); - $thumb->close(); - $file->largethumb = $prefix."largethumb".$filestorename; - } - } - - // add to this user's file folders - file_add_to_folders($folder,$container_guid,$plugin); - - add_to_river("river/object/$plugin/create",'create',$_SESSION['user']->guid,$file->guid); - } else { - break; - } - } else { - break; - } - } - - if ($quota_exceeded) { - echo elgg_echo("$plugin:quotaexceeded"); - } else if ($bad_mime_type) { - echo elgg_echo("$plugin:badmimetype"); - } else if ($result) { - if ($number_of_files > 1) { - echo elgg_echo("$plugin:saved_multi"); - } else { - echo elgg_echo("$plugin:saved"); - } - } else { - if ($number_of_files > 1) { - echo elgg_echo("$plugin:uploadfailed_multi"); - } else { - echo elgg_echo("$plugin:uploadfailed"); - } - } -} - -function file_add_to_folders($folder,$container_guid,$plugin) { - if ($container_guid && ($container = get_entity($container_guid))) { - $folder_field_name = 'elgg_'.$plugin.'_folders'; - $folders = $container->$folder_field_name; - if ($folders) { - if (is_array($folders)) { - if (!in_array($folder,$folders)) { - $folders[] = $folder; - $container->$folder_field_name = $folders; - } - } else { - if ($folders != $folder) { - $container->$folder_field_name = array($folders,$folder); - } - } - } else { - $container->$folder_field_name = $folder; - } - } -} - -function file_handle_save($forward,$plugin) { - // Get variables - $title = get_input("title"); - $desc = get_input("description"); - $tags = get_input("tags"); - $folder = get_input("folder_text"); - if (!$folder) { - $folder = get_input("folder_select"); - } - $access_id = (int) get_input("access_id"); - - $guid = (int) get_input('file_guid'); - - if (!$file = get_entity($guid)) { - register_error(elgg_echo("$plugin:uploadfailed")); - forward($forward . $_SESSION['user']->username); - exit; - } - - $result = false; - - $container_guid = $file->container_guid; - $container = get_entity($container_guid); - - if ($file->canEdit()) { - $file->access_id = $access_id; - $file->title = $title; - $file->description = $desc; - $file->folder = $folder; - // add to this user's file folders - file_add_to_folders($folder,$container_guid,$plugin); - - // Save tags - $tags = explode(",", $tags); - $file->tags = $tags; - - $result = $file->save(); - } - - if ($result) { - system_message(elgg_echo("$plugin:saved")); - } else { - register_error(elgg_echo("$plugin:uploadfailed")); - } - forward($forward . $container->username); -} - /** - * Manage a file download. + * Delete a directory and all its contents * - * @param unknown_type $plugin - * @param unknown_type $file_guid If not specified then file_guid will be found in input. - */ -function file_manage_download($plugin, $file_guid = "") { - // Get the guid - $file_guid = (int)$file_guid; - - if (!$file_guid) { - $file_guid = (int)get_input("file_guid"); - } - - // Get the file - $file = get_entity($file_guid); - - if ($file) { - $mime = $file->getMimeType(); - if (!$mime) { - $mime = "application/octet-stream"; - } - - $filename = $file->originalfilename; - - header("Content-type: $mime"); - if (strpos($mime, "image/")!==false) { - header("Content-Disposition: inline; filename=\"$filename\""); - } else { - header("Content-Disposition: attachment; filename=\"$filename\""); - } - - echo $file->grabFile(); - exit; - } else { - register_error(elgg_echo("$plugin:downloadfailed")); - } -} - -/** - * Manage the download of a file icon. + * @param string $directory Directory to delete * - * @param unknown_type $plugin - * @param unknown_type $file_guid The guid, if not specified this is obtained from the input. - */ -function file_manage_icon_download($plugin, $file_guid = "") { - // Get the guid - $file_guid = (int)$file_guid; - - if (!$file_guid) { - $file_guid = (int)get_input("file_guid"); - } - - // Get the file - $file = get_entity($file_guid); - - if ($file) { - $mime = $file->getMimeType(); - if (!$mime) { - $mime = "application/octet-stream"; - } - - $filename = $file->thumbnail; - - header("Content-type: $mime"); - if (strpos($mime, "image/")!==false) { - header("Content-Disposition: inline; filename=\"$filename\""); - } else { - header("Content-Disposition: attachment; filename=\"$filename\""); - } - - $readfile = new ElggFile(); - $readfile->owner_guid = $file->owner_guid; - $readfile->setFilename($filename); - - /* - if ($file->open("read")); - { - while (!$file->eof()) - { - echo $file->read(10240, $file->tell()); - } - } - */ - - $contents = $readfile->grabFile(); - if (empty($contents)) { - echo file_get_contents(dirname(dirname(__FILE__)) . "/graphics/icons/general.jpg" ); - } else { - echo $contents; - } - exit; - } else { - register_error(elgg_echo("$plugin:downloadfailed")); - } -} - -function file_display_thumbnail($file_guid,$size) { - // Get file entity - if ($file = get_entity($file_guid)) { - $simpletype = $file->simpletype; - if ($simpletype == "image") { - // Get file thumbnail - if ($size == "small") { - $thumbfile = $file->smallthumb; - } else { - $thumbfile = $file->largethumb; - } - - // Grab the file - if ($thumbfile && !empty($thumbfile)) { - $readfile = new ElggFile(); - $readfile->owner_guid = $file->owner_guid; - $readfile->setFilename($thumbfile); - $mime = $file->getMimeType(); - $contents = $readfile->grabFile(); - - header("Content-type: $mime"); - echo $contents; - exit; - } - } - } -} - -function file_set_page_owner($file) { - $page_owner = page_owner_entity(); - if ($page_owner === false || is_null($page_owner)) { - $container_guid = $file->container_guid; - if (!empty($container_guid)) { - if ($page_owner = get_entity($container_guid)) { - set_page_owner($page_owner->guid); - } - } - - if (empty($page_owner)) { - $page_owner = $_SESSION['user']; - set_page_owner($_SESSION['guid']); - } - } -} - -/** - * Recursively delete a directory - * - * @param str $directory + * @return bool */ function delete_directory($directory) { // sanity check: must be a directory @@ -1409,7 +417,11 @@ function delete_directory($directory) { /** * Removes all user files * - * @param ElggUser $user + * @warning This only deletes the physical files and not their entities. + * This will result in FileExceptions being thrown. Don't use this function. + * + * @param ElggUser $user And ElggUser + * * @return void */ function clear_user_files($user) { @@ -1439,6 +451,10 @@ function get_default_filestore() { /** * Set the default filestore for the system. + * + * @param ElggFilestore $filestore An ElggFilestore object. + * + * @return true */ function set_default_filestore(ElggFilestore $filestore) { global $DEFAULT_FILE_STORE; @@ -1449,7 +465,11 @@ function set_default_filestore(ElggFilestore $filestore) { } /** - * Run once and only once. + * Register entity type objects, subtype file as + * ElggFile. + * + * @return void + * @access private */ function filestore_run_once() { // Register a class @@ -1458,25 +478,43 @@ function filestore_run_once() { /** * Initialise the file modules. - * Listens to system boot and registers any appropriate file types and classes + * Listens to system init and configures the default filestore + * + * @return void + * @access private */ function filestore_init() { global $CONFIG; // Now register a default filestore - set_default_filestore(new ElggDiskFilestore($CONFIG->dataroot)); - + if (isset($CONFIG->dataroot)) { + set_default_filestore(new ElggDiskFilestore($CONFIG->dataroot)); + } + // Now run this stuff, but only once run_function_once("filestore_run_once"); } -// Register a startup event -register_elgg_event_handler('init', 'system', 'filestore_init', 100); - -// Unit testing -register_plugin_hook('unit_test', 'system', 'filestore_test'); +/** + * Unit tests for files + * + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params + * + * @return array + * @access private + */ function filestore_test($hook, $type, $value, $params) { global $CONFIG; $value[] = "{$CONFIG->path}engine/tests/objects/filestore.php"; return $value; } + + +// Register a startup event +elgg_register_event_handler('init', 'system', 'filestore_init', 100); + +// Unit testing +elgg_register_plugin_hook_handler('unit_test', 'system', 'filestore_test'); diff --git a/engine/lib/group.php b/engine/lib/group.php index a87dbf071..6ded8a825 100644 --- a/engine/lib/group.php +++ b/engine/lib/group.php @@ -1,319 +1,20 @@ <?php /** * Elgg Groups. - * Groups contain other entities, or rather act as a placeholder for other entities to mark any given container - * as their container. + * Groups contain other entities, or rather act as a placeholder for other entities to + * mark any given container as their container. * - * @package Elgg - * @subpackage Core - - * @author Curverider Ltd - - * @link http://elgg.org/ - */ - -/** - * @class ElggGroup Class representing a container for other elgg entities. - * @author Curverider Ltd + * @package Elgg.Core + * @subpackage DataModel.Group */ -class ElggGroup extends ElggEntity - implements Friendable { - - protected function initialise_attributes() { - parent::initialise_attributes(); - - $this->attributes['type'] = "group"; - $this->attributes['name'] = ""; - $this->attributes['description'] = ""; - $this->attributes['tables_split'] = 2; - } - - /** - * Construct a new user entity, optionally from a given id value. - * - * @param mixed $guid If an int, load that GUID. - * If a db row then will attempt to load the rest of the data. - * @throws Exception if there was a problem creating the user. - */ - function __construct($guid = null) { - $this->initialise_attributes(); - - if (!empty($guid)) { - // Is $guid is a DB row - either a entity row, or a user table row. - if ($guid instanceof stdClass) { - // Load the rest - if (!$this->load($guid->guid)) { - throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid)); - } - } - // Is $guid is an ElggGroup? Use a copy constructor - else if ($guid instanceof ElggGroup) { - elgg_deprecated_notice('This type of usage of the ElggGroup constructor was deprecated. Please use the clone method.', 1.7); - - foreach ($guid->attributes as $key => $value) { - $this->attributes[$key] = $value; - } - } - // Is this is an ElggEntity but not an ElggGroup = ERROR! - else if ($guid instanceof ElggEntity) { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggGroup')); - } - // We assume if we have got this far, $guid is an int - else if (is_numeric($guid)) { - if (!$this->load($guid)) { - IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid)); - } - } - - else { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); - } - } - } - - /** - * Add an ElggObject to this group. - * - * @param ElggObject $object The object. - * @return bool - */ - public function addObjectToGroup(ElggObject $object) { - return add_object_to_group($this->getGUID(), $object->getGUID()); - } - - /** - * Remove an object from the containing group. - * - * @param int $guid The guid of the object. - * @return bool - */ - public function removeObjectFromGroup($guid) { - return remove_object_from_group($this->getGUID(), $guid); - } - - public function get($name) { - if ($name == 'username') { - return 'group:' . $this->getGUID(); - } - return parent::get($name); - } - -/** - * Start friendable compatibility block: - * - * public function addFriend($friend_guid); - public function removeFriend($friend_guid); - public function isFriend(); - public function isFriendsWith($user_guid); - public function isFriendOf($user_guid); - public function getFriends($subtype = "", $limit = 10, $offset = 0); - public function getFriendsOf($subtype = "", $limit = 10, $offset = 0); - public function getObjects($subtype="", $limit = 10, $offset = 0); - public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0); - public function countObjects($subtype = ""); - */ - - /** - * For compatibility with Friendable - */ - public function addFriend($friend_guid) { - return $this->join(get_entity($friend_guid)); - } - - /** - * For compatibility with Friendable - */ - public function removeFriend($friend_guid) { - return $this->leave(get_entity($friend_guid)); - } - - /** - * For compatibility with Friendable - */ - public function isFriend() { - return $this->isMember(); - } - - /** - * For compatibility with Friendable - */ - public function isFriendsWith($user_guid) { - return $this->isMember($user_guid); - } - - /** - * For compatibility with Friendable - */ - public function isFriendOf($user_guid) { - return $this->isMember($user_guid); - } - - /** - * For compatibility with Friendable - */ - public function getFriends($subtype = "", $limit = 10, $offset = 0) { - return get_group_members($this->getGUID(), $limit, $offset); - } - - /** - * For compatibility with Friendable - */ - public function getFriendsOf($subtype = "", $limit = 10, $offset = 0) { - return get_group_members($this->getGUID(), $limit, $offset); - } - - /** - * Get objects contained in this group. - * - * @param string $subtype - * @param int $limit - * @param int $offset - * @return mixed - */ - public function getObjects($subtype="", $limit = 10, $offset = 0) { - return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false); - } - - /** - * For compatibility with Friendable - */ - public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0) { - return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false); - } - - /** - * For compatibility with Friendable - */ - public function countObjects($subtype = "") { - return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", 10, 0, true); - } - -/** - * End friendable compatibility block - */ - - /** - * Get a list of group members. - * - * @param int $limit - * @param int $offset - * @return mixed - */ - public function getMembers($limit = 10, $offset = 0, $count = false) { - return get_group_members($this->getGUID(), $limit, $offset, 0 , $count); - } - - /** - * Returns whether the current group is public membership or not. - * @return bool - */ - public function isPublicMembership() { - if ($this->membership == ACCESS_PUBLIC) { - return true; - } - - return false; - } - - /** - * Return whether a given user is a member of this group or not. - * - * @param ElggUser $user The user - * @return bool - */ - public function isMember($user = 0) { - if (!($user instanceof ElggUser)) { - $user = get_loggedin_user(); - } - if (!($user instanceof ElggUser)) { - return false; - } - return is_group_member($this->getGUID(), $user->getGUID()); - } - - /** - * Join an elgg user to this group. - * - * @param ElggUser $user - * @return bool - */ - public function join(ElggUser $user) { - return join_group($this->getGUID(), $user->getGUID()); - } - - /** - * Remove a user from the group. - * - * @param ElggUser $user - */ - public function leave(ElggUser $user) { - return leave_group($this->getGUID(), $user->getGUID()); - } - - /** - * Override the load function. - * This function will ensure that all data is loaded (were possible), so - * if only part of the ElggGroup is loaded, it'll load the rest. - * - * @param int $guid - */ - protected function load($guid) { - // Test to see if we have the generic stuff - if (!parent::load($guid)) { - return false; - } - - // Check the type - if ($this->attributes['type']!='group') { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class())); - } - - // Load missing data - $row = get_group_entity_as_row($guid); - if (($row) && (!$this->isFullyLoaded())) { - // If $row isn't a cached copy then increment the counter - $this->attributes['tables_loaded'] ++; - } - - // Now put these into the attributes array as core values - $objarray = (array) $row; - foreach($objarray as $key => $value) { - $this->attributes[$key] = $value; - } - - return true; - } - - /** - * Override the save function. - */ - public function save() { - // Save generic stuff - if (!parent::save()) { - return false; - } - - // Now save specific stuff - return create_group_entity($this->get('guid'), $this->get('name'), $this->get('description')); - } - - // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// - - /** - * Return an array of fields which can be exported. - */ - public function getExportableValues() { - return array_merge(parent::getExportableValues(), array( - 'name', - 'description', - )); - } -} /** * Get the group entity. * - * @param int $guid + * @param int $guid GUID for a group + * + * @return array|false + * @access private */ function get_group_entity_as_row($guid) { global $CONFIG; @@ -324,12 +25,15 @@ function get_group_entity_as_row($guid) { } /** - * Create or update the extras table for a given group. + * Create or update the entities table for a given group. * Call create_entity first. * - * @param int $guid - * @param string $name - * @param string $description + * @param int $guid GUID + * @param string $name Name + * @param string $description Description + * + * @return bool + * @access private */ function create_group_entity($guid, $name, $description) { global $CONFIG; @@ -342,28 +46,32 @@ function create_group_entity($guid, $name, $description) { if ($row) { // Exists and you have access to it - if ($exists = get_data_row("SELECT guid from {$CONFIG->dbprefix}groups_entity WHERE guid = {$guid}")) { - $result = update_data("UPDATE {$CONFIG->dbprefix}groups_entity set name='$name', description='$description' where guid=$guid"); - if ($result!=false) { + $exists = get_data_row("SELECT guid from {$CONFIG->dbprefix}groups_entity WHERE guid = {$guid}"); + if ($exists) { + $query = "UPDATE {$CONFIG->dbprefix}groups_entity set" + . " name='$name', description='$description' where guid=$guid"; + $result = update_data($query); + if ($result != false) { // Update succeeded, continue $entity = get_entity($guid); - if (trigger_elgg_event('update',$entity->type,$entity)) { + if (elgg_trigger_event('update', $entity->type, $entity)) { return $guid; } else { $entity->delete(); - //delete_entity($guid); } } } else { // Update failed, attempt an insert. - $result = insert_data("INSERT into {$CONFIG->dbprefix}groups_entity (guid, name, description) values ($guid, '$name','$description')"); - if ($result!==false) { + $query = "INSERT into {$CONFIG->dbprefix}groups_entity" + . " (guid, name, description) values ($guid, '$name', '$description')"; + + $result = insert_data($query); + if ($result !== false) { $entity = get_entity($guid); - if (trigger_elgg_event('create',$entity->type,$entity)) { + if (elgg_trigger_event('create', $entity->type, $entity)) { return $guid; } else { $entity->delete(); - //delete_entity($guid); } } } @@ -372,28 +80,14 @@ function create_group_entity($guid, $name, $description) { return false; } - -/** - * THIS FUNCTION IS DEPRECATED. - * - * Delete a group's extra data. - * - * @param int $guid The guid of the group - * @return bool - */ -function delete_group_entity($guid) { - system_message(sprintf(elgg_echo('deprecatedfunction'), 'delete_user_entity')); - - // Always return that we have deleted one row in order to not break existing code. - return 1; -} - /** * Add an object to the given group. * - * @param int $group_guid The group to add the object to. + * @param int $group_guid The group to add the object to. * @param int $object_guid The guid of the elgg object (must be ElggObject or a child thereof) + * * @return bool + * @throws InvalidClassException */ function add_object_to_group($group_guid, $object_guid) { $group_guid = (int)$group_guid; @@ -407,11 +101,13 @@ function add_object_to_group($group_guid, $object_guid) { } if (!($group instanceof ElggGroup)) { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $group_guid, 'ElggGroup')); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($group_guid, 'ElggGroup')); + throw new InvalidClassException($msg); } if (!($object instanceof ElggObject)) { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $object_guid, 'ElggObject')); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($object_guid, 'ElggObject')); + throw new InvalidClassException($msg); } $object->container_guid = $group_guid; @@ -421,8 +117,11 @@ function add_object_to_group($group_guid, $object_guid) { /** * Remove an object from the given group. * - * @param int $group_guid The group to remove the object from + * @param int $group_guid The group to remove the object from * @param int $object_guid The object to remove + * + * @return bool + * @throws InvalidClassException */ function remove_object_from_group($group_guid, $object_guid) { $group_guid = (int)$group_guid; @@ -436,11 +135,13 @@ function remove_object_from_group($group_guid, $object_guid) { } if (!($group instanceof ElggGroup)) { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $group_guid, 'ElggGroup')); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($group_guid, 'ElggGroup')); + throw new InvalidClassException($msg); } if (!($object instanceof ElggObject)) { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $object_guid, 'ElggObject')); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($object_guid, 'ElggObject')); + throw new InvalidClassException($msg); } $object->container_guid = $object->owner_guid; @@ -448,312 +149,31 @@ function remove_object_from_group($group_guid, $object_guid) { } /** - * Return an array of objects in a given container. - * @see get_entities() + * Return a list of this group's members. * - * @param int $group_guid The container (defaults to current page owner) - * @param string $subtype The subtype - * @param int $owner_guid Owner - * @param int $site_guid The site - * @param string $order_by Order - * @param unknown_type $limit Limit on number of elements to return, by default 10. - * @param unknown_type $offset Where to start, by default 0. - * @param unknown_type $count Whether to return the entities or a count of them. - */ -function get_objects_in_group($group_guid, $subtype = "", $owner_guid = 0, $site_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false) { - global $CONFIG; - - if ($subtype === false || $subtype === null || $subtype === 0) { - return false; - } - - $subtype = get_subtype_id('object', $subtype); - - 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; - } - - $container_guid = (int)$group_guid; - if ($container_guid == 0) { - $container_guid = page_owner(); - } - - $where = array(); - - $where[] = "e.type='object'"; - if ($subtype!=="") { - $where[] = "e.subtype=$subtype"; - } - if ($owner_guid != "") { - if (!is_array($owner_guid)) { - $owner_guid = (int) $owner_guid; - $where[] = "e.container_guid = '$owner_guid'"; - } else if (sizeof($owner_guid) > 0) { - // Cast every element to the owner_guid array to int - $owner_guid = array_map("sanitise_int", $owner_guid); - $owner_guid = implode(",",$owner_guid); - $where[] = "e.container_guid in ({$owner_guid})"; - } - } - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - - if ($container_guid > 0) { - $where[] = "e.container_guid = {$container_guid}"; - } - - if (!$count) { - $query = "SELECT * from {$CONFIG->dbprefix}entities e join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid where "; - } else { - $query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e join {$CONFIG->dbprefix}objects_entity o on e.guid=o.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"; - - // 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; - } -} - -/** - * Get all the entities from metadata from a group. + * @param int $group_guid The ID of the container/group. + * @param int $limit The limit + * @param int $offset The offset + * @param int $site_guid The site + * @param bool $count Return the users (false) or the count of them (true) * - * @param int $group_guid The ID of the group. - * @param mixed $meta_name - * @param mixed $meta_value - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $limit - * @param int $offset - * @param string $order_by Optional ordering. - * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. - * @param true|false $count If set to true, returns the total number of entities rather than a list. (Default: false) - */ -function get_entities_from_metadata_groups($group_guid, $meta_name, $meta_value = "", $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { - global $CONFIG; - - $meta_n = get_metastring_id($meta_name); - $meta_v = get_metastring_id($meta_value); - - $entity_type = sanitise_string($entity_type); - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - $limit = (int)$limit; - $offset = (int)$offset; - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - $order_by = sanitise_string($order_by); - $site_guid = (int) $site_guid; - if (is_array($owner_guid)) { - foreach($owner_guid as $key => $guid) { - $owner_guid[$key] = (int) $guid; - } - } else { - $owner_guid = (int) $owner_guid; - } - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $container_guid = (int)$group_guid; - if ($container_guid == 0) { - $container_guid = page_owner(); - } - - //$access = get_access_list(); - - $where = array(); - - if ($entity_type!="") { - $where[] = "e.type='$entity_type'"; - } - if ($entity_subtype) { - $where[] = "e.subtype=$entity_subtype"; - } - if ($meta_name!="") { - $where[] = "m.name_id='$meta_n'"; - } - if ($meta_value!="") { - $where[] = "m.value_id='$meta_v'"; - } - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - if ($container_guid > 0) { - $where[] = "e.container_guid = {$container_guid}"; - } - - if (is_array($owner_guid)) { - $where[] = "e.container_guid in (".implode(",",$owner_guid).")"; - } else if ($owner_guid > 0) - $where[] = "e.container_guid = {$owner_guid}"; - - if (!$count) { - $query = "SELECT distinct e.* "; - } else { - $query = "SELECT count(e.guid) as total "; - } - - $query .= "from {$CONFIG->dbprefix}entities e JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid join {$CONFIG->dbprefix}objects_entity o on e.guid = o.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 limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($row = get_data_row($query)) { - return $row->total; - } - } - return false; -} - -/** - * As get_entities_from_metadata_groups() but with multiple entities. - * - * @param int $group_guid The ID of the group. - * @param array $meta_array Array of 'name' => 'value' pairs - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $limit - * @param int $offset - * @param string $order_by Optional ordering. - * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. - * @param true|false $count If set to true, returns the total number of entities rather than a list. (Default: false) - * @return int|array List of ElggEntities, or the total number if count is set to false + * @return mixed */ -function get_entities_from_metadata_groups_multi($group_guid, $meta_array, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { - global $CONFIG; - - if (!is_array($meta_array) || sizeof($meta_array) == 0) { - return false; - } - - $where = array(); - - $mindex = 1; - $join = ""; - foreach($meta_array as $meta_name => $meta_value) { - $meta_n = get_metastring_id($meta_name); - $meta_v = get_metastring_id($meta_value); - $join .= " JOIN {$CONFIG->dbprefix}metadata m{$mindex} on e.guid = m{$mindex}.entity_guid join {$CONFIG->dbprefix}objects_entity o on e.guid = o.guid "; - if ($meta_name!="") { - $where[] = "m{$mindex}.name_id='$meta_n'"; - } - - if ($meta_value!="") { - $where[] = "m{$mindex}.value_id='$meta_v'"; - } - - $mindex++; - } - - $entity_type = sanitise_string($entity_type); - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - $limit = (int)$limit; - $offset = (int)$offset; - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - $order_by = sanitise_string($order_by); - $owner_guid = (int) $owner_guid; - - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - //$access = get_access_list(); - - if ($entity_type!="") { - $where[] = "e.type = '{$entity_type}'"; - } - - if ($entity_subtype) { - $where[] = "e.subtype = {$entity_subtype}"; - } - - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - - if ($owner_guid > 0) { - $where[] = "e.owner_guid = {$owner_guid}"; - } - - if ($container_guid > 0) { - $where[] = "e.container_guid = {$container_guid}"; - } - - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT distinct e.* "; - } - - $query .= " from {$CONFIG->dbprefix}entities e {$join} where"; - foreach ($where as $w) { - $query .= " $w and "; - } - $query .= get_access_sql_suffix("e"); // Add access controls +function get_group_members($group_guid, $limit = 10, $offset = 0, $site_guid = 0, $count = false) { - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } + // in 1.7 0 means "not set." rewrite to make sense. + if (!$site_guid) { + $site_guid = ELGG_ENTITIES_ANY_VALUE; } - return false; -} -/** - * Return a list of this group's members. - * - * @param int $group_guid The ID of the container/group. - * @param int $limit The limit - * @param int $offset The offset - * @param int $site_guid The site - * @param bool $count Return the users (false) or the count of them (true) - * @return mixed - */ -function get_group_members($group_guid, $limit = 10, $offset = 0, $site_guid = 0, $count = false) { return elgg_get_entities_from_relationship(array( - 'relationship' => 'member', - 'relationship_guid' => $group_guid, - 'inverse_relationship' => TRUE, - 'types' => 'user', - 'limit' => $limit, - 'offset' => $offset, - 'count' => $count, + 'relationship' => 'member', + 'relationship_guid' => $group_guid, + 'inverse_relationship' => TRUE, + 'type' => 'user', + 'limit' => $limit, + 'offset' => $offset, + 'count' => $count, 'site_guid' => $site_guid )); } @@ -762,7 +182,8 @@ function get_group_members($group_guid, $limit = 10, $offset = 0, $site_guid = 0 * Return whether a given user is a member of the group or not. * * @param int $group_guid The group ID - * @param int $user_guid The user guid + * @param int $user_guid The user guid + * * @return bool */ function is_group_member($group_guid, $user_guid) { @@ -777,12 +198,19 @@ function is_group_member($group_guid, $user_guid) { /** * Join a user to a group. * - * @param int $group_guid The group. - * @param int $user_guid The user. + * @param int $group_guid The group GUID. + * @param int $user_guid The user GUID. + * + * @return bool */ function join_group($group_guid, $user_guid) { $result = add_entity_relationship($user_guid, 'member', $group_guid); - trigger_elgg_event('join', 'group', array('group' => get_entity($group_guid), 'user' => get_entity($user_guid))); + + if ($result) { + $params = array('group' => get_entity($group_guid), 'user' => get_entity($user_guid)); + elgg_trigger_event('join', 'group', $params); + } + return $result; } @@ -790,11 +218,15 @@ function join_group($group_guid, $user_guid) { * Remove a user from a group. * * @param int $group_guid The group. - * @param int $user_guid The user. + * @param int $user_guid The user. + * + * @return bool */ function leave_group($group_guid, $user_guid) { // event needs to be triggered while user is still member of group to have access to group acl - trigger_elgg_event('leave', 'group', array('group' => get_entity($group_guid), 'user' => get_entity($user_guid))); + $params = array('group' => get_entity($group_guid), 'user' => get_entity($user_guid)); + + elgg_trigger_event('leave', 'group', $params); $result = remove_entity_relationship($user_guid, 'member', $group_guid); return $result; } @@ -802,57 +234,73 @@ function leave_group($group_guid, $user_guid) { /** * Return all groups a user is a member of. * - * @param unknown_type $user_guid + * @param int $user_guid GUID of user + * + * @return array|false */ function get_users_membership($user_guid) { - return elgg_get_entities_from_relationship(array('relationship' => 'member', 'relationship_guid' => $user_guid, 'inverse_relationship' => FALSE)); + $options = array( + 'type' => 'group', + 'relationship' => 'member', + 'relationship_guid' => $user_guid, + 'inverse_relationship' => false, + 'limit' => false, + ); + return elgg_get_entities_from_relationship($options); } /** - * Checks access to a group. + * May the current user access item(s) on this page? If the page owner is a group, + * membership, visibility, and logged in status are taken into account. + * + * @param boolean $forward If set to true (default), will forward the page; + * if set to false, will return true or false. * - * @param boolean $forward If set to true (default), will forward the page; if set to false, will return true or false. - * @return true|false If $forward is set to false. + * @return bool If $forward is set to false. */ function group_gatekeeper($forward = true) { - $allowed = true; - $url = ''; - - if ($group = page_owner_entity()) { - if ($group instanceof ElggGroup) { - $url = $group->getURL(); - if ( - ((!isloggedin()) && (!$group->isPublicMembership())) || - ((!$group->isMember(get_loggedin_user()) && (!$group->isPublicMembership()))) - ) { - $allowed = false; - } - // Admin override - if (isadminloggedin()) { - $allowed = true; - } - } + $page_owner_guid = elgg_get_page_owner_guid(); + if (!$page_owner_guid) { + return true; } + $visibility = ElggGroupItemVisibility::factory($page_owner_guid); - if ($forward && $allowed == false) { - forward($url); - exit; + if (!$visibility->shouldHideItems) { + return true; } + if ($forward) { + // only forward to group if user can see it + $group = get_entity($page_owner_guid); + $forward_url = $group ? $group->getURL() : ''; - return $allowed; + if (!elgg_is_logged_in()) { + $_SESSION['last_forward_from'] = current_page_url(); + $forward_reason = 'login'; + } else { + $forward_reason = 'member'; + } + + register_error(elgg_echo($visibility->reasonHidden)); + forward($forward_url, $forward_reason); + } + + return false; } /** - * Manages group tool options + * Adds a group tool option * - * @param string $name Name of the group tool option - * @param string $label Used for the group edit form - * @param boolean $default_on True if this option should be active by default + * @see remove_group_tool_option(). * - **/ - -function add_group_tool_option($name,$label,$default_on=true) { + * @param string $name Name of the group tool option + * @param string $label Used for the group edit form + * @param bool $default_on True if this option should be active by default + * + * @return void + * @since 1.5.0 + */ +function add_group_tool_option($name, $label, $default_on = true) { global $CONFIG; if (!isset($CONFIG->group_tool_options)) { @@ -869,104 +317,25 @@ function add_group_tool_option($name,$label,$default_on=true) { } /** - * Searches for a group based on a complete or partial name or description + * Removes a group tool option based on name * - * @param string $criteria The partial or full name or description - * @param int $limit Limit of the search. - * @param int $offset Offset. - * @param string $order_by The order. - * @param boolean $count Whether to return the count of results or just the results. - * @deprecated 1.7 + * @see add_group_tool_option() + * + * @param string $name Name of the group tool option + * + * @return void + * @since 1.7.5 */ -function search_for_group($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { - elgg_deprecated_notice('search_for_group() was deprecated by new search plugin.', 1.7); +function remove_group_tool_option($name) { global $CONFIG; - $criteria = sanitise_string($criteria); - $limit = (int)$limit; - $offset = (int)$offset; - $order_by = sanitise_string($order_by); - - $access = get_access_sql_suffix("e"); - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT e.* "; - } - $query .= "from {$CONFIG->dbprefix}entities e join {$CONFIG->dbprefix}groups_entity g on e.guid=g.guid where "; - // $query .= " match(u.name,u.username) against ('$criteria') "; - $query .= "(g.name like \"%{$criteria}%\" or g.description like \"%{$criteria}%\")"; - $query .= " and $access"; - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } + if (!isset($CONFIG->group_tool_options)) { + return; } - return false; -} - -/** - * Returns a formatted list of groups suitable for injecting into search. - * @deprecated 1.7 - */ -function search_list_groups_by_name($hook, $user, $returnvalue, $tag) { - elgg_deprecated_notice('search_list_groups_by_name() was deprecated by new search plugin', 1.7); - // Change this to set the number of groups that display on the search page - $threshold = 4; - $object = get_input('object'); - - if (!get_input('offset') && (empty($object) || $object == 'group')) { - if ($groups = search_for_group($tag,$threshold)) { - $countgroups = search_for_group($tag,0,0,"",true); - - $return = elgg_view('group/search/startblurb',array('count' => $countgroups, 'tag' => $tag)); - foreach($groups as $group) { - $return .= elgg_view_entity($group); - } - $return .= elgg_view('group/search/finishblurb',array('count' => $countgroups, 'threshold' => $threshold, 'tag' => $tag)); - return $return; + foreach ($CONFIG->group_tool_options as $i => $option) { + if ($option->name == $name) { + unset($CONFIG->group_tool_options[$i]); } } } - -/** - * Displays a list of group objects that have been searched for. - * - * @see elgg_view_entity_list - * - * @param string $tag Search criteria - * @param int $limit The number of entities to display on a page - * @return string The list in a form suitable to display - * @deprecated 1.7 - */ -function list_group_search($tag, $limit = 10) { - elgg_deprecated_notice('list_group_search() was deprecated by new search plugin.', 1.7); - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = (int) search_for_group($tag, 10, 0, '', true); - $entities = search_for_group($tag, $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); - -} - -/** - * Performs initialisation functions for groups - * - */ -function group_init() { - // Register an entity type - register_entity_type('group',''); -} - -register_elgg_event_handler('init','system','group_init'); diff --git a/engine/lib/input.php b/engine/lib/input.php index d6f044c90..80b0b8766 100644 --- a/engine/lib/input.php +++ b/engine/lib/input.php @@ -3,51 +3,56 @@ * Parameter input functions. * This file contains functions for getting input from get/post variables. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Input */ /** - * Get some input from variables passed on the GET or POST line. + * Get some input from variables passed submitted through GET or POST. + * + * If using any data obtained from get_input() in a web page, please be aware that + * it is a possible vector for a reflected XSS attack. If you are expecting an + * integer, cast it to an int. If it is a string, escape quotes. * * Note: this function does not handle nested arrays (ex: form input of param[m][n]) * because of the filtering done in htmlawed from the filter_tags call. + * @todo Is this ^ still true? + * + * @param string $variable The variable name we want. + * @param mixed $default A default value for the variable if it is not found. + * @param bool $filter_result If true, then the result is filtered for bad tags. * - * @param $variable string The variable we want to return. - * @param $default mixed A default value for the variable if it is not found. - * @param $filter_result If true then the result is filtered for bad tags. + * @return mixed */ -function get_input($variable, $default = "", $filter_result = true) { +function get_input($variable, $default = NULL, $filter_result = TRUE) { global $CONFIG; + $result = $default; + + elgg_push_context('input'); + if (isset($CONFIG->input[$variable])) { - $var = $CONFIG->input[$variable]; + $result = $CONFIG->input[$variable]; if ($filter_result) { - $var = filter_tags($var); + $result = filter_tags($result); } - - return $var; - } - - if (isset($_REQUEST[$variable])) { + } elseif (isset($_REQUEST[$variable])) { if (is_array($_REQUEST[$variable])) { - $var = $_REQUEST[$variable]; + $result = $_REQUEST[$variable]; } else { - $var = trim($_REQUEST[$variable]); + $result = trim($_REQUEST[$variable]); } if ($filter_result) { - $var = filter_tags($var); + $result = filter_tags($result); } - - return $var; } - return $default; + elgg_pop_context(); + + return $result; } /** @@ -55,8 +60,10 @@ function get_input($variable, $default = "", $filter_result = true) { * * Note: this function does not handle nested arrays (ex: form input of param[m][n]) * - * @param string $variable The name of the variable - * @param string $value The value of the variable + * @param string $variable The name of the variable + * @param string|string[] $value The value of the variable + * + * @return void */ function set_input($variable, $value) { global $CONFIG; @@ -65,10 +72,7 @@ function set_input($variable, $value) { } if (is_array($value)) { - foreach ($value as $key => $val) { - $value[$key] = trim($val); - } - + array_walk_recursive($value, create_function('&$v, $k', '$v = trim($v);')); $CONFIG->input[trim($variable)] = $value; } else { $CONFIG->input[trim($variable)] = trim($value); @@ -77,279 +81,379 @@ function set_input($variable, $value) { /** * Filter tags from a given string based on registered hooks. - * @param $var - * @return mixed The filtered result + * + * @param mixed $var Anything that does not include an object (strings, ints, arrays) + * This includes multi-dimensional arrays. + * + * @return mixed The filtered result - everything will be strings */ function filter_tags($var) { - return trigger_plugin_hook('validate', 'input', null, $var); + return elgg_trigger_plugin_hook('validate', 'input', null, $var); } /** - * Sanitise file paths for input, ensuring that they begin and end with slashes etc. + * Validates an email address. + * + * @param string $address Email address. * - * @param string $path The path - * @return string + * @return bool */ -function sanitise_filepath($path) { - // Convert to correct UNIX paths - $path = str_replace('\\', '/', $path); +function is_email_address($address) { + return filter_var($address, FILTER_VALIDATE_EMAIL) === $address; +} - // Sort trailing slash - $path = trim($path); - // rtrim defaults plus / - $path = rtrim($path, " \n\t\0\x0B/"); - $path = $path . "/"; +/** + * Load all the REQUEST variables into the sticky form cache + * + * Call this from an action when you want all your submitted variables + * available if the submission fails validation and is sent back to the form + * + * @param string $form_name Name of the sticky form + * + * @return void + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_make_sticky_form($form_name) { - return $path; -} + elgg_clear_sticky_form($form_name); + + if (!isset($_SESSION['sticky_forms'])) { + $_SESSION['sticky_forms'] = array(); + } + $_SESSION['sticky_forms'][$form_name] = array(); + foreach ($_REQUEST as $key => $var) { + // will go through XSS filtering on the get function + $_SESSION['sticky_forms'][$form_name][$key] = $var; + } +} /** - * Takes a string and turns any URLs into formatted links - * - * @param string $text The input string - * @return string The output stirng with formatted links - **/ -function parse_urls($text) { - // @todo this causes problems with <attr = "val"> - // must be ing <attr="val"> format (no space). - // By default htmlawed rewrites tags to this format. - // if PHP supported conditional negative lookbehinds we could use this: - // $r = preg_replace_callback('/(?<!=)(?<![ ])?(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', - // - // we can put , in the list of excluded char but need to keep . because of domain names. - // it is removed in the callback. - $r = preg_replace_callback('/(?<!=)(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', - create_function( - '$matches', - ' - $url = $matches[1]; - $period = \'\'; - if (substr($url, -1, 1) == \'.\') { - $period = \'.\'; - $url = trim($url, \'.\'); - } - $urltext = str_replace("/", "/<wbr />", $url); - return "<a href=\"$url\" style=\"text-decoration:underline;\">$urltext</a>$period"; - ' - ), $text); + * Clear the sticky form cache + * + * Call this if validation is successful in the action handler or + * when they sticky values have been used to repopulate the form + * after a validation error. + * + * @param string $form_name Form namespace + * + * @return void + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_clear_sticky_form($form_name) { + unset($_SESSION['sticky_forms'][$form_name]); +} - return $r; +/** + * Has this form been made sticky? + * + * @param string $form_name Form namespace + * + * @return boolean + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_is_sticky_form($form_name) { + return isset($_SESSION['sticky_forms'][$form_name]); } /** + * Get a specific sticky variable * - * Adds P tags. - * Borrowed from Wordpress. - * - **/ -function autop($pee, $br = 1) { - $pee = $pee . "\n"; // just to make things a little easier, pad the end - $pee = preg_replace('|<br />\s*<br />|', "\n\n", $pee); - // Space things out a little - $allblocks = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|map|area|blockquote|address|math|style|input|p|h[1-6]|hr)'; - $pee = preg_replace('!(<' . $allblocks . '[^>]*>)!', "\n$1", $pee); - $pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee); - $pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines - if ( strpos($pee, '<object') !== false ) { - $pee = preg_replace('|\s*<param([^>]*)>\s*|', "<param$1>", $pee); // no pee inside object/embed - $pee = preg_replace('|\s*</embed>\s*|', '</embed>', $pee); - } - $pee = preg_replace("/\n\n+/", "\n\n", $pee); // take care of duplicates - $pee = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "<p>$1</p>\n", $pee); // make paragraphs, including one at the end - $pee = preg_replace('|<p>\s*?</p>|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace - $pee = preg_replace('!<p>([^<]+)\s*?(</(?:div|address|form)[^>]*>)!', "<p>$1</p>$2", $pee); - $pee = preg_replace( '|<p>|', "$1<p>", $pee ); - $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); // don't pee all over a tag - $pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // problem with nested lists - $pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee); - $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee); - $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee); - $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); - if ($br) { - $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', create_function('$matches', 'return str_replace("\n", "<WPPreserveNewline />", $matches[0]);'), $pee); - $pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee); // optionally make line breaks - $pee = str_replace('<WPPreserveNewline />', "\n", $pee); + * @param string $form_name The name of the form + * @param string $variable The name of the variable + * @param mixed $default Default value if the variable does not exist in sticky cache + * @param boolean $filter_result Filter for bad input if true + * + * @return mixed + * + * @todo should this filter the default value? + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_get_sticky_value($form_name, $variable = '', $default = NULL, $filter_result = true) { + if (isset($_SESSION['sticky_forms'][$form_name][$variable])) { + $value = $_SESSION['sticky_forms'][$form_name][$variable]; + if ($filter_result) { + // XSS filter result + $value = filter_tags($value); + } + return $value; } - $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', "$1", $pee); - $pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee); -// if (strpos($pee, '<pre') !== false) { -// mind the space between the ? and >. Only there because of the comment. -// $pee = preg_replace_callback('!(<pre.*? >)(.*?)</pre>!is', 'clean_pre', $pee ); -// } - $pee = preg_replace( "|\n</p>$|", '</p>', $pee ); - - return $pee; + return $default; } /** - * Examins $_SERVER['REQUEST_URI'] and set_input()s on each. - * Required if the params are sent as GET and not forwarded by mod_rewrite. + * Get all the values in a sticky form in an array + * + * @param string $form_name The name of the form + * @param bool $filter_result Filter for bad input if true * - * @return bool on success + * @return array + * @since 1.8.0 */ -function elgg_set_input_from_uri() { - $query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); - $query_arr = elgg_parse_str($query); +function elgg_get_sticky_values($form_name, $filter_result = true) { + if (!isset($_SESSION['sticky_forms'][$form_name])) { + return array(); + } - if (is_array($query_arr)) { - foreach($query_arr as $name => $val) { - set_input($name, $val); + $values = $_SESSION['sticky_forms'][$form_name]; + if ($filter_result) { + foreach ($values as $key => $value) { + // XSS filter result + $values[$key] = filter_tags($value); } } + return $values; +} + +/** + * Clear a specific sticky variable + * + * @param string $form_name The name of the form + * @param string $variable The name of the variable to clear + * + * @return void + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_clear_sticky_value($form_name, $variable) { + unset($_SESSION['sticky_forms'][$form_name][$variable]); } /** * Page handler for autocomplete endpoint. * - * @param $page - * @return unknown_type + * @todo split this into functions/objects, this is way too big + * + * /livesearch?q=<query> + * + * Other options include: + * match_on string all or array(groups|users|friends) + * match_owner int 0/1 + * limit int default is 10 + * + * @param array $page + * @return string JSON string is returned and then exit + * @access private */ function input_livesearch_page_handler($page) { global $CONFIG; + // only return results to logged in users. - if (!$user = get_loggedin_user()) { + if (!$user = elgg_get_logged_in_user_entity()) { exit; } - if (!$q = get_input('q')) { + if (!$q = get_input('term', get_input('q'))) { exit; } - $q = mysql_real_escape_string($q); + $q = sanitise_string($q); // replace mysql vars with escaped strings $q = str_replace(array('_', '%'), array('\_', '\%'), $q); $match_on = get_input('match_on', 'all'); - if ($match_on == 'all' || $match_on[0] == 'all') { - $match_on = array('users', 'groups'); - } if (!is_array($match_on)) { $match_on = array($match_on); } + // all = users and groups + if (in_array('all', $match_on)) { + $match_on = array('users', 'groups'); + } + if (get_input('match_owner', false)) { - $owner_guid = $user->getGUID(); $owner_where = 'AND e.owner_guid = ' . $user->getGUID(); } else { - $owner_guid = null; $owner_where = ''; } - $limit = get_input('limit', 10); + $limit = sanitise_int(get_input('limit', 10)); // grab a list of entities and send them in json. $results = array(); - foreach ($match_on as $type) { - switch ($type) { - case 'all': - // only need to pull up title from objects. - - if (!$entities = elgg_get_entities(array('owner_guid' => $owner_guid, 'limit' => $limit)) AND is_array($entities)) { - $results = array_merge($results, $entities); - } - break; - + foreach ($match_on as $match_type) { + switch ($match_type) { case 'users': $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as ue, {$CONFIG->dbprefix}entities as e WHERE e.guid = ue.guid AND e.enabled = 'yes' AND ue.banned = 'no' - AND (ue.name LIKE '$q%' OR ue.username LIKE '$q%') + AND (ue.name LIKE '$q%' OR ue.name LIKE '% $q%' OR ue.username LIKE '$q%') LIMIT $limit "; if ($entities = get_data($query)) { foreach ($entities as $entity) { - $json = json_encode(array( + // @todo use elgg_get_entities (don't query in a loop!) + $entity = get_entity($entity->guid); + /* @var ElggUser $entity */ + if (!$entity) { + continue; + } + + if (in_array('groups', $match_on)) { + $value = $entity->guid; + } else { + $value = $entity->username; + } + + $output = elgg_view_list_item($entity, array( + 'use_hover' => false, + 'class' => 'elgg-autocomplete-item', + )); + + $icon = elgg_view_entity_icon($entity, 'tiny', array( + 'use_hover' => false, + )); + + $result = array( 'type' => 'user', 'name' => $entity->name, 'desc' => $entity->username, - 'icon' => '<img class="livesearch_icon" src="' . get_entity($entity->guid)->getIcon('tiny') . '" />', - 'guid' => $entity->guid - )); - $results[$entity->name . rand(1,100)] = $json; + 'guid' => $entity->guid, + 'label' => $output, + 'value' => $value, + 'icon' => $icon, + 'url' => $entity->getURL(), + ); + $results[$entity->name . rand(1, 100)] = $result; } } break; case 'groups': // don't return results if groups aren't enabled. - if (!is_plugin_enabled('groups')) { + if (!elgg_is_active_plugin('groups')) { continue; } $query = "SELECT * FROM {$CONFIG->dbprefix}groups_entity as ge, {$CONFIG->dbprefix}entities as e WHERE e.guid = ge.guid AND e.enabled = 'yes' $owner_where - AND (ge.name LIKE '$q%' OR ge.description LIKE '%$q%') + AND (ge.name LIKE '$q%' OR ge.name LIKE '% $q%' OR ge.description LIKE '% $q%') LIMIT $limit "; if ($entities = get_data($query)) { foreach ($entities as $entity) { - $json = json_encode(array( + // @todo use elgg_get_entities (don't query in a loop!) + $entity = get_entity($entity->guid); + /* @var ElggGroup $entity */ + if (!$entity) { + continue; + } + + $output = elgg_view_list_item($entity, array( + 'use_hover' => false, + 'class' => 'elgg-autocomplete-item', + )); + + $icon = elgg_view_entity_icon($entity, 'tiny', array( + 'use_hover' => false, + )); + + $result = array( 'type' => 'group', 'name' => $entity->name, 'desc' => strip_tags($entity->description), - 'icon' => '<img class="livesearch_icon" src="' . get_entity($entity->guid)->getIcon('tiny') . '" />', - 'guid' => $entity->guid - )); - //$results[$entity->name . rand(1,100)] = "$json|{$entity->guid}"; - $results[$entity->name . rand(1,100)] = $json; + 'guid' => $entity->guid, + 'label' => $output, + 'value' => $entity->guid, + 'icon' => $icon, + 'url' => $entity->getURL(), + ); + + $results[$entity->name . rand(1, 100)] = $result; } } break; case 'friends': - $access = get_access_sql_suffix(); - $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as ue, {$CONFIG->dbprefix}entity_relationships as er, {$CONFIG->dbprefix}entities as e + $query = "SELECT * FROM + {$CONFIG->dbprefix}users_entity as ue, + {$CONFIG->dbprefix}entity_relationships as er, + {$CONFIG->dbprefix}entities as e WHERE er.relationship = 'friend' AND er.guid_one = {$user->getGUID()} AND er.guid_two = ue.guid AND e.guid = ue.guid AND e.enabled = 'yes' AND ue.banned = 'no' - AND (ue.name LIKE '$q%' OR ue.username LIKE '$q%') + AND (ue.name LIKE '$q%' OR ue.name LIKE '% $q%' OR ue.username LIKE '$q%') LIMIT $limit "; if ($entities = get_data($query)) { foreach ($entities as $entity) { - $json = json_encode(array( + // @todo use elgg_get_entities (don't query in a loop!) + $entity = get_entity($entity->guid); + /* @var ElggUser $entity */ + if (!$entity) { + continue; + } + + $output = elgg_view_list_item($entity, array( + 'use_hover' => false, + 'class' => 'elgg-autocomplete-item', + )); + + $icon = elgg_view_entity_icon($entity, 'tiny', array( + 'use_hover' => false, + )); + + $result = array( 'type' => 'user', 'name' => $entity->name, 'desc' => $entity->username, - 'icon' => '<img class="livesearch_icon" src="' . get_entity($entity->guid)->getIcon('tiny') . '" />', - 'guid' => $entity->guid - )); - $results[$entity->name . rand(1,100)] = $json; + 'guid' => $entity->guid, + 'label' => $output, + 'value' => $entity->username, + 'icon' => $icon, + 'url' => $entity->getURL(), + ); + $results[$entity->name . rand(1, 100)] = $result; } } break; default: - // arbitrary subtype. - //@todo you cannot specify a subtype without a type. - // did this ever work? - elgg_get_entities(array('subtype' => $type, 'owner_guid' => $owner_guid)); + header("HTTP/1.0 400 Bad Request", true); + echo "livesearch: unknown match_on of $match_type"; + exit; break; } } ksort($results); - echo implode($results, "\n"); + header("Content-Type: application/json"); + echo json_encode(array_values($results)); exit; } - +/** + * Register input functions and sanitize input + * + * @return void + * @access private + */ function input_init() { // register an endpoint for live search / autocomplete. - register_page_handler('livesearch', 'input_livesearch_page_handler'); + elgg_register_page_handler('livesearch', 'input_livesearch_page_handler'); + + if (ini_get_bool('magic_quotes_gpc')) { - if (ini_get_bool('magic_quotes_gpc') ) { - //do keys as well, cos array_map ignores them + /** + * do keys as well, cos array_map ignores them + * + * @param array $array Array of values + * + * @return array Sanitized array + */ function stripslashes_arraykeys($array) { if (is_array($array)) { $array2 = array(); @@ -366,6 +470,13 @@ function input_init() { } } + /** + * Strip slashes on everything + * + * @param mixed $value The value to remove slashes from + * + * @return mixed + */ function stripslashes_deep($value) { if (is_array($value)) { $value = stripslashes_arraykeys($value); @@ -406,4 +517,4 @@ function input_init() { } } -register_elgg_event_handler('init','system','input_init'); +elgg_register_event_handler('init', 'system', 'input_init'); diff --git a/engine/lib/install.php b/engine/lib/install.php deleted file mode 100644 index 1b363b950..000000000 --- a/engine/lib/install.php +++ /dev/null @@ -1,117 +0,0 @@ -<?php - -/** - * Elgg installation - * Various functions to assist with installing and upgrading the system - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - -/** - * Check that the installed version of PHP meets the minimum requirements (currently 5.2 or greater). - * - * @return bool - */ -function php_check_version() { - if (version_compare(phpversion(), '5.1.2', '>=')) { - return true; - } - - return false; -} - -/** - * Validate the platform Elgg is being installed on. - * - * @throws ConfigurationException if the validation fails. - * @return bool - */ -function validate_platform() { - // Get database version - if (!db_check_version()) { - throw new ConfigurationException(elgg_echo('ConfigurationException:BadDatabaseVersion')); - } - - // Now check PHP - if (!php_check_version()) { - throw new ConfigurationException(elgg_echo('ConfigurationException:BadPHPVersion')); - } - - // TODO: Consider checking for installed modules etc - return true; -} - -/** - * Returns whether or not the database has been installed - * - * @return true|false Whether the database has been installed - */ -function is_db_installed() { - global $CONFIG; - - if (isset($CONFIG->db_installed)) { - return $CONFIG->db_installed; - } - - if ($dblink = get_db_link('read')) { - mysql_query("select name from {$CONFIG->dbprefix}datalists limit 1", $dblink); - if (mysql_errno($dblink) > 0) { - return false; - } - } else { - return false; - } - - // Set flag if db is installed (if false then we want to check every time) - $CONFIG->db_installed = true; - - return true; -} - -/** - * Returns whether or not other settings have been set - * - * @return true|false Whether or not the rest of the installation has been followed through with - */ -function is_installed() { - global $CONFIG; - return datalist_get('installed'); -} - -/** - * Copy and create a new settings.php from settings.example.php, substituting the variables in - * $vars where appropriate. - * - * $vars is an associate array of $key => $value, where $key is the variable text you wish to substitute (eg - * CONFIG_DBNAME will replace {{CONFIG_DBNAME}} in the settings file. - * - * @param array $vars The array of vars - * @param string $in_file Optional input file (if not settings.example.php) - * @return string The file containing substitutions. - */ -function create_settings(array $vars, $in_file="engine/settings.example.php") { - $file = file_get_contents($in_file); - - if (!$file) { - return false; - } - - foreach ($vars as $k => $v) { - $file = str_replace("{{".$k."}}", $v, $file); - } - - return $file; -} - -/** - * Initialisation for installation functions - * - */ -function install_init() { - register_action("systemsettings/install",true); -} - -register_elgg_event_handler("boot","system","install_init");
\ No newline at end of file diff --git a/engine/lib/languages.php b/engine/lib/languages.php index 98283f141..61ba91ddb 100644 --- a/engine/lib/languages.php +++ b/engine/lib/languages.php @@ -3,13 +3,70 @@ * Elgg language module * Functions to manage language and translations. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Languages */ /** + * Given a message key, returns an appropriately translated full-text string + * + * @param string $message_key The short message code + * @param array $args An array of arguments to pass through vsprintf(). + * @param string $language Optionally, the standard language code + * (defaults to site/user default, then English) + * + * @return string Either the translated string, the English string, + * or the original language string. + */ +function elgg_echo($message_key, $args = array(), $language = "") { + global $CONFIG; + + static $CURRENT_LANGUAGE; + + // old param order is deprecated + if (!is_array($args)) { + elgg_deprecated_notice( + 'As of Elgg 1.8, the 2nd arg to elgg_echo() is an array of string replacements and the 3rd arg is the language.', + 1.8 + ); + + $language = $args; + $args = array(); + } + + if (!isset($CONFIG->translations)) { + // this means we probably had an exception before translations were initialized + register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); + } + + if (!$CURRENT_LANGUAGE) { + $CURRENT_LANGUAGE = get_language(); + } + if (!$language) { + $language = $CURRENT_LANGUAGE; + } + + if (isset($CONFIG->translations[$language][$message_key])) { + $string = $CONFIG->translations[$language][$message_key]; + } else if (isset($CONFIG->translations["en"][$message_key])) { + $string = $CONFIG->translations["en"][$message_key]; + $lang = $CONFIG->translations["en"][$language]; + elgg_log(sprintf('Missing %s translation for "%s" language key', $lang, $message_key), 'NOTICE'); + } else { + $string = $message_key; + elgg_log(sprintf('Missing English translation for "%s" language key', $message_key), 'NOTICE'); + } + + // only pass through if we have arguments to allow backward compatibility + // with manual sprintf() calls. + if ($args) { + $string = vsprintf($string, $args); + } + + return $string; +} + +/** * Add a translation. * * Translations are arrays in the Zend Translation array format, eg: @@ -17,9 +74,10 @@ * $english = array('message1' => 'message1', 'message2' => 'message2'); * $german = array('message1' => 'Nachricht1','message2' => 'Nachricht2'); * - * @param string $country_code Standard country code (eg 'en', 'nl', 'es') - * @param array $language_array Formatted array of strings - * @return true|false Depending on success + * @param string $country_code Standard country code (eg 'en', 'nl', 'es') + * @param array $language_array Formatted array of strings + * + * @return bool Depending on success */ function add_translation($country_code, $language_array) { global $CONFIG; @@ -46,8 +104,6 @@ function add_translation($country_code, $language_array) { * @return string The language code for the site/user or "en" if not set */ function get_current_language() { - global $CONFIG; - $language = get_language(); if (!$language) { @@ -65,7 +121,7 @@ function get_current_language() { function get_language() { global $CONFIG; - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); $language = false; if (($user) && ($user->language)) { @@ -84,90 +140,137 @@ function get_language() { } /** - * Given a message shortcode, returns an appropriately translated full-text string - * - * @param string $message_key The short message code - * @param string $language Optionally, the standard language code (defaults to site/user default, then English) - * @return string Either the translated string or the original English string + * @access private */ -function elgg_echo($message_key, $language = "") { +function _elgg_load_translations() { global $CONFIG; - static $CURRENT_LANGUAGE; - if (!$CURRENT_LANGUAGE) { - $CURRENT_LANGUAGE = get_language(); - } - if (!$language) { - $language = $CURRENT_LANGUAGE; - } + if ($CONFIG->system_cache_enabled) { + $loaded = true; + $languages = array_unique(array('en', get_current_language())); + foreach ($languages as $language) { + $data = elgg_load_system_cache("$language.lang"); + if ($data) { + add_translation($language, unserialize($data)); + } else { + $loaded = false; + } + } - if (isset($CONFIG->translations[$language][$message_key])) { - return $CONFIG->translations[$language][$message_key]; - } else if (isset($CONFIG->translations["en"][$message_key])) { - return $CONFIG->translations["en"][$message_key]; + if ($loaded) { + $CONFIG->i18n_loaded_from_cache = true; + // this is here to force + $CONFIG->language_paths[dirname(dirname(dirname(__FILE__))) . "/languages/"] = true; + return; + } } - return $message_key; + // load core translations from languages directory + register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); } + + /** * When given a full path, finds translation files and loads them * - * @param string $path Full path - * @param bool $load_all If true all languages are loaded, if false only the current language + en are loaded + * @param string $path Full path + * @param bool $load_all If true all languages are loaded, if + * false only the current language + en are loaded + * + * @return bool success */ function register_translations($path, $load_all = false) { global $CONFIG; + $path = sanitise_filepath($path); + // Make a note of this path just incase we need to register this language later - if(!isset($CONFIG->language_paths)) $CONFIG->language_paths = array(); + if (!isset($CONFIG->language_paths)) { + $CONFIG->language_paths = array(); + } $CONFIG->language_paths[$path] = true; // Get the current language based on site defaults and user preference $current_language = get_current_language(); elgg_log("Translations loaded from: $path"); - if ($handle = opendir($path)) { - while ($language = readdir($handle)) { - if ( - ((in_array($language, array('en.php', $current_language . '.php'))) /*&& (!is_dir($path . $language))*/) || - (($load_all) && (strpos($language, '.php')!==false)/* && (!is_dir($path . $language))*/) - ) { - include_once($path . $language); + // only load these files unless $load_all is true. + $load_language_files = array( + 'en.php', + "$current_language.php" + ); + + $load_language_files = array_unique($load_language_files); + + $handle = opendir($path); + if (!$handle) { + elgg_log("Could not open language path: $path", 'ERROR'); + return false; + } + + $return = true; + while (false !== ($language = readdir($handle))) { + // ignore bad files + if (substr($language, 0, 1) == '.' || substr($language, -4) !== '.php') { + continue; + } + + if (in_array($language, $load_language_files) || $load_all) { + if (!include_once($path . $language)) { + $return = false; + continue; } } - } else { - elgg_log("Missing translation path $path", 'ERROR'); } + + return $return; } /** * Reload all translations from all registered paths. * - * This is only called by functions which need to know all possible translations, namely the - * statistic gathering ones. + * This is only called by functions which need to know all possible translations. * - * TODO: Better on demand loading based on language_paths array + * @todo Better on demand loading based on language_paths array * - * @return bool + * @return void */ function reload_all_translations() { global $CONFIG; static $LANG_RELOAD_ALL_RUN; if ($LANG_RELOAD_ALL_RUN) { - return null; + return; } - foreach ($CONFIG->language_paths as $path => $dummy) { - register_translations($path, true); + if ($CONFIG->i18n_loaded_from_cache) { + $cache = elgg_get_system_cache(); + $cache_dir = $cache->getVariable("cache_path"); + $filenames = elgg_get_file_list($cache_dir, array(), array(), array(".lang")); + foreach ($filenames as $filename) { + if (preg_match('/([a-z]+)\.[^.]+$/', $filename, $matches)) { + $language = $matches[1]; + $data = elgg_load_system_cache("$language.lang"); + if ($data) { + add_translation($language, unserialize($data)); + } + } + } + } else { + foreach ($CONFIG->language_paths as $path => $dummy) { + register_translations($path, true); + } } $LANG_RELOAD_ALL_RUN = true; } /** - * Return an array of installed translations as an associative array "two letter code" => "native language name". + * Return an array of installed translations as an associative + * array "two letter code" => "native language name". + * + * @return array */ function get_installed_translations() { global $CONFIG; @@ -178,8 +281,8 @@ function get_installed_translations() { $installed = array(); foreach ($CONFIG->translations as $k => $v) { - $installed[$k] = elgg_echo($k, $k); - if (isadminloggedin()) { + $installed[$k] = elgg_echo($k, array(), $k); + if (elgg_is_admin_logged_in()) { $completeness = get_language_completeness($k); if (($completeness < 100) && ($k != 'en')) { $installed[$k] .= " (" . $completeness . "% " . elgg_echo('complete') . ")"; @@ -192,6 +295,10 @@ function get_installed_translations() { /** * Return the level of completeness for a given language code (compared to english) + * + * @param string $language Language + * + * @return int */ function get_language_completeness($language) { global $CONFIG; @@ -217,7 +324,12 @@ function get_language_completeness($language) { } /** - * Return the translation keys missing from a given language, or those that are identical to the english version. + * Return the translation keys missing from a given language, + * or those that are identical to the english version. + * + * @param string $language The language + * + * @return mixed */ function get_missing_language_keys($language) { global $CONFIG; @@ -240,5 +352,3 @@ function get_missing_language_keys($language) { return false; } - -register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); diff --git a/engine/lib/location.php b/engine/lib/location.php index 21ee7d5fa..1534c7d7b 100644 --- a/engine/lib/location.php +++ b/engine/lib/location.php @@ -2,67 +2,31 @@ /** * Elgg geo-location tagging library. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Location */ /** - * Define an interface for geo-tagging entities. - * - */ -interface Locatable { - /** Set a location text */ - public function setLocation($location); - - /** - * Set latitude and longitude tags for a given entity. - * - * @param float $lat - * @param float $long - */ - public function setLatLong($lat, $long); - - /** - * Get the contents of the ->geo:lat field. - * - */ - public function getLatitude(); - - /** - * Get the contents of the ->geo:lat field. - * - */ - public function getLongitude(); - - /** - * Get the ->location metadata. - * - */ - public function getLocation(); -} - -/** * Encode a location into a latitude and longitude, caching the result. * - * Works by triggering the 'geocode' 'location' plugin hook, and requires a geocoding module to be installed - * activated in order to work. + * Works by triggering the 'geocode' 'location' plugin + * hook, and requires a geocoding plugin to be installed. * - * @param String $location The location, e.g. "London", or "24 Foobar Street, Gotham City" + * @param string $location The location, e.g. "London", or "24 Foobar Street, Gotham City" + * @return string|false */ function elgg_geocode_location($location) { global $CONFIG; - // Handle cases where we are passed an array (shouldn't be but can happen if location is a tag field) if (is_array($location)) { - $location = implode(', ', $location); + return false; } $location = sanitise_string($location); // Look for cached version - $cached_location = get_data_row("SELECT * from {$CONFIG->dbprefix}geocode_cache WHERE location='$location'"); + $query = "SELECT * from {$CONFIG->dbprefix}geocode_cache WHERE location='$location'"; + $cached_location = get_data_row($query); if ($cached_location) { return array('lat' => $cached_location->lat, 'long' => $cached_location->long); @@ -70,7 +34,7 @@ function elgg_geocode_location($location) { // Trigger geocode event if not cached $return = false; - $return = trigger_plugin_hook('geocode', 'location', array('location' => $location), $return); + $return = elgg_trigger_plugin_hook('geocode', 'location', array('location' => $location), $return); // If returned, cache and return value if (($return) && (is_array($return))) { @@ -78,7 +42,10 @@ function elgg_geocode_location($location) { $long = (float)$return['long']; // Put into cache at the end of the page since we don't really care that much - execute_delayed_write_query("INSERT DELAYED INTO {$CONFIG->dbprefix}geocode_cache (location, lat, `long`) VALUES ('$location', '{$lat}', '{$long}') ON DUPLICATE KEY UPDATE lat='{$lat}', `long`='{$long}'"); + $query = "INSERT DELAYED INTO {$CONFIG->dbprefix}geocode_cache " + . " (location, lat, `long`) VALUES ('$location', '{$lat}', '{$long}')" + . " ON DUPLICATE KEY UPDATE lat='{$lat}', `long`='{$long}'"; + execute_delayed_write_query($query); } return $return; @@ -87,196 +54,104 @@ function elgg_geocode_location($location) { /** * Return entities within a given geographic area. * - * @param real $lat Latitude - * @param real $long Longitude - * @param real $radius The radius - * @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. + * Also accepts all options available to elgg_get_entities(). + * + * @see elgg_get_entities + * + * @param array $options Array in format: + * + * latitude => FLOAT Latitude of the location + * + * longitude => FLOAT Longitude of the location + * + * distance => FLOAT/ARR ( + * latitude => float, + * longitude => float, + * ) + * The distance in degrees that determines the search box. A + * single float will result in a square in degrees. + * @warning The Earth is round. + * + * @see ElggEntity::setLatLong() + * + * @return mixed If count, int. If not count, array. false on errors. + * @since 1.8.0 */ -function get_entities_in_area($lat, $long, $radius, $type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid) { - global $CONFIG; +function elgg_get_entities_from_location(array $options = array()) { - if ($subtype === false || $subtype === null || $subtype === 0) { + global $CONFIG; + + if (!isset($options['latitude']) || !isset($options['longitude']) || + !isset($options['distance'])) { return false; } - $lat = (real)$lat; - $long = (real)$long; - $radius = (real)$radius; - - $order_by = sanitise_string($order_by); - $limit = (int)$limit; - $offset = (int)$offset; - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $where = array(); - - if (is_array($type)) { - $tempwhere = ""; - if (sizeof($type)) { - foreach($type as $typekey => $subtypearray) { - foreach($subtypearray as $subtypeval) { - $typekey = sanitise_string($typekey); - if (!empty($subtypeval)) { - $subtypeval = (int) get_subtype_id($typekey, $subtypeval); - } else { - $subtypeval = 0; - } - if (!empty($tempwhere)) $tempwhere .= " or "; - $tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; - } - } - } - if (!empty($tempwhere)) { - $where[] = "({$tempwhere})"; - } + if (!is_array($options['distance'])) { + $lat_distance = (float)$options['distance']; + $long_distance = (float)$options['distance']; } else { - $type = sanitise_string($type); - $subtype = get_subtype_id($type, $subtype); - - 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[] = "e.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 = implode(",",$owner_guid); // - $where[] = "e.owner_guid in ({$owner_guid})" ; // - } - if (is_null($container_guid)) { - $container_guid = $owner_array; - } + $lat_distance = (float)$options['distance']['latitude']; + $long_distance = (float)$options['distance']['longitude']; } - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; + $lat = (float)$options['latitude']; + $long = (float)$options['longitude']; + $lat_min = $lat - $lat_distance; + $lat_max = $lat + $lat_distance; + $long_min = $long - $long_distance; + $long_max = $long + $long_distance; + + $wheres = array(); + $wheres[] = "lat_name.string='geo:lat'"; + $wheres[] = "lat_value.string >= $lat_min"; + $wheres[] = "lat_value.string <= $lat_max"; + $wheres[] = "lon_name.string='geo:long'"; + $wheres[] = "lon_value.string >= $long_min"; + $wheres[] = "lon_value.string <= $long_max"; + + $joins = array(); + $joins[] = "JOIN {$CONFIG->dbprefix}metadata lat on e.guid=lat.entity_guid"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings lat_name on lat.name_id=lat_name.id"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings lat_value on lat.value_id=lat_value.id"; + $joins[] = "JOIN {$CONFIG->dbprefix}metadata lon on e.guid=lon.entity_guid"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings lon_name on lon.name_id=lon_name.id"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings lon_value on lon.value_id=lon_value.id"; + + // merge wheres to pass to get_entities() + if (isset($options['wheres']) && !is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } elseif (!isset($options['wheres'])) { + $options['wheres'] = array(); } + $options['wheres'] = array_merge($options['wheres'], $wheres); - 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}"; - } - } - - // Add the calendar stuff - $loc_join = " - JOIN {$CONFIG->dbprefix}metadata loc_start on e.guid=loc_start.entity_guid - JOIN {$CONFIG->dbprefix}metastrings loc_start_name on loc_start.name_id=loc_start_name.id - JOIN {$CONFIG->dbprefix}metastrings loc_start_value on loc_start.value_id=loc_start_value.id - - JOIN {$CONFIG->dbprefix}metadata loc_end on e.guid=loc_end.entity_guid - JOIN {$CONFIG->dbprefix}metastrings loc_end_name on loc_end.name_id=loc_end_name.id - JOIN {$CONFIG->dbprefix}metastrings loc_end_value on loc_end.value_id=loc_end_value.id - "; - - $lat_min = $lat - $radius; - $lat_max = $lat + $radius; - $long_min = $long - $radius; - $long_max = $long + $radius; - - $where[] = "loc_start_name.string='geo:lat'"; - $where[] = "loc_start_value.string>=$lat_min"; - $where[] = "loc_start_value.string<=$lat_max"; - $where[] = "loc_end_name.string='geo:long'"; - $where[] = "loc_end_value.string >= $long_min"; - $where[] = "loc_end_value.string <= $long_max"; - - if (!$count) { - $query = "SELECT e.* from {$CONFIG->dbprefix}entities e $loc_join where "; - } else { - $query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e $loc_join where "; - } - foreach ($where as $w) { - $query .= " $w and "; + // merge joins to pass to get_entities() + if (isset($options['joins']) && !is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } elseif (!isset($options['joins'])) { + $options['joins'] = array(); } + $options['joins'] = array_merge($options['joins'], $joins); - $query .= get_access_sql_suffix('e'); // Add access controls - - if (!$count) { - $query .= " order by n.calendar_start $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; - } + return elgg_get_entities_from_relationship($options); } /** - * List entities in a given location + * Returns a viewable list of entities from location * - * @param string $location Location - * @param string $type The type of entity (eg "user", "object" etc) - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * @param int $limit The number of entities to display per page (default: 10) - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow gallery view - * @param true|false $pagination Display pagination? Default: true - * @return string A viewable list of entities - */ -function list_entities_location($location, $type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false, $navigation = true) { - return list_entities_from_metadata('location', $location, $type, $subtype, $owner_guid, $limit, $fullview, $viewtypetoggle, $navigation); -} - -/** - * List items within a given geographic area. + * @param array $options Options array * - * @param real $lat Latitude - * @param real $long Longitude - * @param real $radius The radius - * @param string $type The type of entity (eg "user", "object" etc) - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * @param int $limit The number of entities to display per page (default: 10) - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow gallery view - * @param true|false $pagination Display pagination? Default: true - * @return string A viewable list of entities + * @see elgg_list_entities() + * @see elgg_get_entities_from_location() + * + * @return string The viewable list of entities + * @since 1.8.0 */ -function list_entities_in_area($lat, $long, $radius, $type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false, $navigation = true) { - - $offset = (int) get_input('offset'); - $count = get_entities_in_area($lat, $long, $radius, $type, $subtype, $owner_guid, "", $limit, $offset, true); - $entities = get_entities_in_area($lat, $long, $radius, $type, $subtype, $owner_guid, "", $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $navigation); +function elgg_list_entities_from_location(array $options = array()) { + return elgg_list_entities($options, 'elgg_get_entities_from_location'); } // Some distances in degrees (approximate) +// @todo huh? see warning on elgg_get_entities_from_location() define("MILE", 0.01515); define("KILOMETER", 0.00932); - -// TODO: get objects within x miles by entities, metadata and relationship - -// TODO: List
\ No newline at end of file diff --git a/engine/lib/mb_wrapper.php b/engine/lib/mb_wrapper.php index 9aa4aac4c..68fa69005 100644 --- a/engine/lib/mb_wrapper.php +++ b/engine/lib/mb_wrapper.php @@ -11,8 +11,10 @@ if (is_callable('mb_internal_encoding')) { * NOTE: This differs from parse_str() by returning the results * instead of placing them in the local scope! * - * @param str $str + * @param string $str The string + * * @return array + * @since 1.7.0 */ function elgg_parse_str($str) { if (is_callable('mb_parse_str')) { @@ -24,44 +26,208 @@ function elgg_parse_str($str) { return $results; } -// map string functions to their mb_str_func alternatives -// and wrap them in elgg_str_fun() - -// list of non-mb safe string functions to wrap in elgg_*() -// only will work with mb_* functions that take the same -// params in the same order as their non-mb safe counterparts. -$str_funcs = array( - // can't wrap parse_str() because of its 2nd parameter. - //'parse_str', - 'split', - 'stristr', - 'strlen', - 'strpos', - 'strrchr', - 'strripos', - 'strrpos', - 'strstr', - 'strtolower', - 'strtoupper', - 'substr_count', - 'substr' -); - -$eval_statement = ''; -foreach ($str_funcs as $func) { - // create wrapper function passing in the same args as given - $mb_func = "mb_$func"; - $eval_statement .= " - function elgg_$func() { - \$args = func_get_args(); - if (is_callable('$mb_func')) { - return call_user_func_array('$mb_func', \$args); - } - return call_user_func_array('$func', \$args); + + +/** + * Wrapper function for mb_split(). Falls back to split() if + * mb_split() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return string + * @since 1.7.0 + */ +function elgg_split() { + $args = func_get_args(); + if (is_callable('mb_split')) { + return call_user_func_array('mb_split', $args); + } + return call_user_func_array('split', $args); +} + +/** + * Wrapper function for mb_stristr(). Falls back to stristr() if + * mb_stristr() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return string + * @since 1.7.0 + */ +function elgg_stristr() { + $args = func_get_args(); + if (is_callable('mb_stristr')) { + return call_user_func_array('mb_stristr', $args); + } + return call_user_func_array('stristr', $args); +} + +/** + * Wrapper function for mb_strlen(). Falls back to strlen() if + * mb_strlen() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return string + * @since 1.7.0 + */ +function elgg_strlen() { + $args = func_get_args(); + if (is_callable('mb_strlen')) { + return call_user_func_array('mb_strlen', $args); + } + return call_user_func_array('strlen', $args); +} + +/** + * Wrapper function for mb_strpos(). Falls back to strpos() if + * mb_strpos() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return string + * @since 1.7.0 + */ +function elgg_strpos() { + $args = func_get_args(); + if (is_callable('mb_strpos')) { + return call_user_func_array('mb_strpos', $args); + } + return call_user_func_array('strpos', $args); +} + +/** + * Wrapper function for mb_strrchr(). Falls back to strrchr() if + * mb_strrchr() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return string + * @since 1.7.0 + */ +function elgg_strrchr() { + $args = func_get_args(); + if (is_callable('mb_strrchr')) { + return call_user_func_array('mb_strrchr', $args); + } + return call_user_func_array('strrchr', $args); +} + +/** + * Wrapper function for mb_strripos(). Falls back to strripos() if + * mb_strripos() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return int + * @since 1.7.0 + */ +function elgg_strripos() { + $args = func_get_args(); + if (is_callable('mb_strripos')) { + return call_user_func_array('mb_strripos', $args); + } + return call_user_func_array('strripos', $args); +} + +/** + * Wrapper function for mb_strrpos(). Falls back to strrpos() if + * mb_strrpos() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return int + * @since 1.7.0 + */ +function elgg_strrpos() { + $args = func_get_args(); + if (is_callable('mb_strrpos')) { + return call_user_func_array('mb_strrpos', $args); + } + return call_user_func_array('strrpos', $args); +} + +/** + * Wrapper function for mb_strstr(). Falls back to strstr() if + * mb_strstr() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return bool + * @since 1.7.0 + */ +function elgg_strstr() { + $args = func_get_args(); + if (is_callable('mb_strstr')) { + return call_user_func_array('mb_strstr', $args); } -"; + return call_user_func_array('strstr', $args); } -eval($eval_statement); +/** + * Wrapper function for mb_strtolower(). Falls back to strtolower() if + * mb_strtolower() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return string + * @since 1.7.0 + */ +function elgg_strtolower() { + $args = func_get_args(); + if (is_callable('mb_strtolower')) { + return call_user_func_array('mb_strtolower', $args); + } + return call_user_func_array('strtolower', $args); +} -// TODO: Other wrapper functions
\ No newline at end of file +/** + * Wrapper function for mb_strtoupper(). Falls back to strtoupper() if + * mb_strtoupper() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return string + * @since 1.7.0 + */ +function elgg_strtoupper() { + $args = func_get_args(); + if (is_callable('mb_strtoupper')) { + return call_user_func_array('mb_strtoupper', $args); + } + return call_user_func_array('strtoupper', $args); +} + +/** + * Wrapper function for mb_substr_count(). Falls back to substr_count() if + * mb_substr_count() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return int + * @since 1.7.0 + */ +function elgg_substr_count() { + $args = func_get_args(); + if (is_callable('mb_substr_count')) { + return call_user_func_array('mb_substr_count', $args); + } + return call_user_func_array('substr_count', $args); +} + +/** + * Wrapper function for mb_substr(). Falls back to substr() if + * mb_substr() isn't available. Parameters are passed to the + * wrapped function in the same order they are passed to this + * function. + * + * @return string + * @since 1.7.0 + */ +function elgg_substr() { + $args = func_get_args(); + if (is_callable('mb_substr')) { + return call_user_func_array('mb_substr', $args); + } + return call_user_func_array('substr', $args); +} diff --git a/engine/lib/memcache.php b/engine/lib/memcache.php index ed93cacf0..79b87e850 100644 --- a/engine/lib/memcache.php +++ b/engine/lib/memcache.php @@ -4,164 +4,11 @@ * * Requires php5-memcache to work. * - * @package Elgg - * @subpackage API - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Cache.Memcache */ /** - * Memcache wrapper class. - * @author Curverider Ltd <info@elgg.com> - */ -class ElggMemcache extends ElggSharedMemoryCache { - /** - * Minimum version of memcached needed to run - * - */ - private static $MINSERVERVERSION = '1.1.12'; - - /** - * Memcache object - */ - private $memcache; - - /** - * Expiry of saved items (default timeout after a day to prevent anything getting too stale) - */ - private $expires = 86400; - - /** - * The version of memcache running - */ - private $version = 0; - - /** - * Connect to memcache. - * - * @param string $cache_id The namespace for this cache to write to - note, namespaces of the same name are shared! - */ - function __construct($namespace = 'default') { - global $CONFIG; - - $this->setNamespace($namespace); - - // Do we have memcache? - if (!class_exists('Memcache')) { - throw new ConfigurationException(elgg_echo('memcache:notinstalled')); - } - - // Create memcache object - $this->memcache = new Memcache; - - // Now add servers - if (!$CONFIG->memcache_servers) { - throw new ConfigurationException(elgg_echo('memcache:noservers')); - } - - if (is_callable($this->memcache, 'addServer')) { - foreach ($CONFIG->memcache_servers as $server) { - if (is_array($server)) { - $this->memcache->addServer( - $server[0], - isset($server[1]) ? $server[1] : 11211, - isset($server[2]) ? $server[2] : true, - isset($server[3]) ? $server[3] : null, - isset($server[4]) ? $server[4] : 1, - isset($server[5]) ? $server[5] : 15, - isset($server[6]) ? $server[6] : true - ); - - } else { - $this->memcache->addServer($server, 11211); - } - } - } else { - elgg_log(elgg_echo('memcache:noaddserver'), 'ERROR'); - - $server = $CONFIG->memcache_servers[0]; - if (is_array($server)) { - $this->memcache->connect($server[0], $server[1]); - } else { - $this->memcache->addServer($server, 11211); - } - } - - // Get version - $this->version = $this->memcache->getversion(); - if (version_compare($this->version, ElggMemcache::$MINSERVERVERSION, '<')) { - throw new ConfigurationException(sprintf(elgg_echo('memcache:versiontoolow'), ElggMemcache::$MINSERVERVERSION, $this->version)); - } - - // Set some defaults - if (isset($CONFIG->memcache_expires)) { - $this->expires = $CONFIG->memcache_expires; - } - } - - /** - * Set the default expiry. - * - * @param int $expires The lifetime as a unix timestamp or time from now. Defaults forever. - */ - public function setDefaultExpiry($expires = 0) { - $this->expires = $expires; - } - - /** - * Combine a key with the namespace. - * Memcache can only accept <250 char key. If the given key is too long it is shortened. - * - * @param string $key The key - * @return string The new key. - */ - private function make_memcache_key($key) { - $prefix = $this->getNamespace() . ":"; - - if (strlen($prefix.$key)> 250) { - $key = md5($key); - } - - return $prefix.$key; - } - - public function save($key, $data) { - $key = $this->make_memcache_key($key); - - $result = $this->memcache->set($key, $data, null, $this->expires); - if (!$result) { - elgg_log("MEMCACHE: FAILED TO SAVE $key", 'ERROR'); - } - - return $result; - } - - public function load($key, $offset = 0, $limit = null) { - $key = $this->make_memcache_key($key); - - $result = $this->memcache->get($key); - if (!$result) { - elgg_log("MEMCACHE: FAILED TO LOAD $key", 'ERROR'); - } - - return $result; - } - - public function delete($key) { - $key = $this->make_memcache_key($key); - - return $this->memcache->delete($key, 0); - } - - public function clear() { - // DISABLE clearing for now - you must use delete on a specific key. - return true; - - //TODO: Namespaces as in #532 - } -} - -/** * Return true if memcache is available and configured. * * @return bool @@ -176,7 +23,7 @@ function is_memcache_available() { } // If we haven't set variable to something - if (($memcache_available!==true) && ($memcache_available!==false)) { + if (($memcache_available !== true) && ($memcache_available !== false)) { try { $tmp = new ElggMemcache(); // No exception thrown so we have memcache available @@ -187,4 +34,24 @@ function is_memcache_available() { } return $memcache_available; +} + +/** + * Invalidate an entity in memcache + * + * @param int $entity_guid The GUID of the entity to invalidate + * + * @return void + * @access private + */ +function _elgg_invalidate_memcache_for_entity($entity_guid) { + static $newentity_cache; +
+ if ((!$newentity_cache) && (is_memcache_available())) {
+ $newentity_cache = new ElggMemcache('new_entity_cache');
+ } +
+ if ($newentity_cache) {
+ $newentity_cache->delete($entity_guid);
+ } }
\ No newline at end of file diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php index 71b6c2634..fdb1b85f6 100644 --- a/engine/lib/metadata.php +++ b/engine/lib/metadata.php @@ -1,132 +1,19 @@ <?php /** * Elgg metadata - * Functions to manage object metadata. + * Functions to manage entity metadata. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage DataModel.Metadata */ /** - * ElggMetadata - * This class describes metadata that can be attached to ElggEntities. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Core - */ -class ElggMetadata extends ElggExtender { - /** - * Construct a new site object, optionally from a given id value or row. - * - * @param mixed $id - */ - function __construct($id = null) { - $this->attributes = array(); - - if (!empty($id)) { - // Create from db row - if ($id instanceof stdClass) { - $metadata = $id; - } else { - $metadata = get_metadata($id); - } - - if ($metadata) { - $objarray = (array) $metadata; - foreach($objarray as $key => $value) { - $this->attributes[$key] = $value; - } - $this->attributes['type'] = "metadata"; - } - } - } - - /** - * 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); - } - - /** - * Determines whether or not the user can edit this piece of metadata - * - * @return true|false Depending on permissions - */ - function canEdit() { - if ($entity = get_entity($this->get('entity_guid'))) { - return $entity->canEditMetadata($this); - } - return false; - } - - /** - * Save matadata object - * - * @return int the metadata object id - */ - function save() { - if ($this->id > 0) { - return update_metadata($this->id, $this->name, $this->value, $this->value_type, $this->owner_guid, $this->access_id); - } else { - $this->id = create_metadata($this->entity_guid, $this->name, $this->value, $this->value_type, $this->owner_guid, $this->access_id); - if (!$this->id) { - throw new IOException(sprintf(elgg_new('IOException:UnableToSaveNew'), get_class())); - } - return $this->id; - } - } - - /** - * Delete a given metadata. - */ - function delete() { - return delete_metadata($this->id); - } - - /** - * Get a url for this item of metadata. - * - * @return string - */ - public function getURL() { - return get_metadata_url($this->id); - } - - // SYSTEM LOG INTERFACE //////////////////////////////////////////////////////////// - - /** - * 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_metadata($id); - } -} - -/** * Convert a database row to a new ElggMetadata * - * @param stdClass $row - * @return stdClass or ElggMetadata + * @param stdClass $row An object from the database + * + * @return stdClass|ElggMetadata + * @access private */ function row_to_elggmetadata($row) { if (!($row instanceof stdClass)) { @@ -137,64 +24,55 @@ function row_to_elggmetadata($row) { } /** - * Get a specific item of metadata. + * Get a specific metadata object by its id. + * If you want multiple metadata objects, use + * {@link elgg_get_metadata()}. + * + * @param int $id The id of the metadata object being retrieved. * - * @param $id int The item of metadata being retrieved. + * @return ElggMetadata|false FALSE if not found */ -function get_metadata($id) { - global $CONFIG; - - $id = (int)$id; - $access = get_access_sql_suffix("e"); - $md_access = get_access_sql_suffix("m"); - - return row_to_elggmetadata(get_data_row("SELECT m.*, n.string as name, v.string as value from {$CONFIG->dbprefix}metadata m JOIN {$CONFIG->dbprefix}entities e on e.guid = m.entity_guid JOIN {$CONFIG->dbprefix}metastrings v on m.value_id = v.id JOIN {$CONFIG->dbprefix}metastrings n on m.name_id = n.id where m.id=$id and $access and $md_access")); +function elgg_get_metadata_from_id($id) { + return elgg_get_metastring_based_object_from_id($id, 'metadata'); } /** - * Removes metadata on an entity with a particular name, optionally with a given value. + * Deletes metadata using its ID. * - * @param int $entity_guid The entity GUID - * @param string $name The name of the metadata - * @param string $value The optional value of the item (useful for removing a single item in a multiple set) - * @return true|false Depending on success + * @param int $id The metadata ID to delete. + * @return bool */ -function remove_metadata($entity_guid, $name, $value = "") { - global $CONFIG; - $entity_guid = (int) $entity_guid; - $name = sanitise_string($name); - $value = sanitise_string($value); - - $query = "SELECT * from {$CONFIG->dbprefix}metadata WHERE entity_guid = $entity_guid and name_id=" . add_metastring($name); - if ($value!="") { - $query .= " and value_id=" . add_metastring($value); - } - - if ($existing = get_data($query)) { - foreach($existing as $ex) { - delete_metadata($ex->id); - } - return true; +function elgg_delete_metadata_by_id($id) { + $metadata = elgg_get_metadata_from_id($id); + if (!$metadata) { + return false; } - - return false; + return $metadata->delete(); } /** * Create a new metadata object, or update an existing one. * - * @param int $entity_guid The entity to attach the metadata to - * @param string $name Name of the metadata - * @param string $value Value of the metadata (cannot be associative array) - * @param string $value_type - * @param int $owner_guid - * @param int $access_id - * @param bool $allow_multiple + * Metadata can be an array by setting allow_multiple to TRUE, but it is an + * indexed array with no control over the indexing. + * + * @param int $entity_guid The entity to attach the metadata to + * @param string $name Name of the metadata + * @param string $value Value of the metadata + * @param string $value_type 'text', 'integer', or '' for automatic detection + * @param int $owner_guid GUID of entity that owns the metadata + * @param int $access_id Default is ACCESS_PRIVATE + * @param bool $allow_multiple Allow multiple values for one key. Default is FALSE + * + * @return int|false id of metadata or FALSE if failure */ -function create_metadata($entity_guid, $name, $value, $value_type, $owner_guid, $access_id = ACCESS_PRIVATE, $allow_multiple = false) { +function create_metadata($entity_guid, $name, $value, $value_type = '', $owner_guid = 0, + $access_id = ACCESS_PRIVATE, $allow_multiple = false) { + global $CONFIG; $entity_guid = (int)$entity_guid; + // name and value are encoded in add_metastring() //$name = sanitise_string(trim($name)); //$value = sanitise_string(trim($value)); $value_type = detect_extender_valuetype($value, sanitise_string(trim($value_type))); @@ -202,81 +80,85 @@ function create_metadata($entity_guid, $name, $value, $value_type, $owner_guid, $owner_guid = (int)$owner_guid; $allow_multiple = (boolean)$allow_multiple; - if ($owner_guid==0) { - $owner_guid = get_loggedin_userid(); + if (!isset($value)) { + return FALSE; + } + + if ($owner_guid == 0) { + $owner_guid = elgg_get_logged_in_user_guid(); } $access_id = (int)$access_id; - $id = false; + $query = "SELECT * from {$CONFIG->dbprefix}metadata" + . " WHERE entity_guid = $entity_guid and name_id=" . add_metastring($name) . " limit 1"; - $existing = get_data_row("SELECT * from {$CONFIG->dbprefix}metadata WHERE entity_guid = $entity_guid and name_id=" . add_metastring($name) . " limit 1"); - if (($existing) && (!$allow_multiple) && (isset($value))) { - $id = $existing->id; + $existing = get_data_row($query); + if ($existing && !$allow_multiple) { + $id = (int)$existing->id; $result = update_metadata($id, $name, $value, $value_type, $owner_guid, $access_id); if (!$result) { return false; } - } - else if (isset($value)) { + } else { // Support boolean types if (is_bool($value)) { - if ($value) { - $value = 1; - } else { - $value = 0; - } + $value = (int) $value; } // Add the metastrings - $value = add_metastring($value); - if (!$value) { + $value_id = add_metastring($value); + if (!$value_id) { return false; } - $name = add_metastring($name); - if (!$name) { + $name_id = add_metastring($name); + if (!$name_id) { return false; } // If ok then add it - $id = insert_data("INSERT into {$CONFIG->dbprefix}metadata (entity_guid, name_id, value_id, value_type, owner_guid, time_created, access_id) VALUES ($entity_guid, '$name','$value','$value_type', $owner_guid, $time, $access_id)"); + $query = "INSERT into {$CONFIG->dbprefix}metadata" + . " (entity_guid, name_id, value_id, value_type, owner_guid, time_created, access_id)" + . " VALUES ($entity_guid, '$name_id','$value_id','$value_type', $owner_guid, $time, $access_id)"; + + $id = insert_data($query); + + if ($id !== false) { + $obj = elgg_get_metadata_from_id($id); + if (elgg_trigger_event('create', 'metadata', $obj)) { - if ($id!==false) { - $obj = get_metadata($id); - if (trigger_elgg_event('create', 'metadata', $obj)) { - return true; + elgg_get_metadata_cache()->save($entity_guid, $name, $value, $allow_multiple); + + return $id; } else { - delete_metadata($id); + elgg_delete_metadata_by_id($id); } } - - } else if ($existing) { - // TODO: Check... are you sure you meant to do this Ben? :) - $id = $existing->id; - delete_metadata($id); } return $id; } /** - * Update an item of metadata. - * - * @param int $id - * @param string $name - * @param string $value - * @param string $value_type - * @param int $owner_guid - * @param int $access_id + * Update a specific piece of metadata. + * + * @param int $id ID of the metadata to update + * @param string $name Metadata name + * @param string $value Metadata value + * @param string $value_type Value type + * @param int $owner_guid Owner guid + * @param int $access_id Access ID + * + * @return bool */ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_id) { global $CONFIG; $id = (int)$id; - if (!$md = get_metadata($id)) { + if (!$md = elgg_get_metadata_from_id($id)) { return false; } if (!$md->canEdit()) { @@ -290,51 +172,50 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i } if ($metabyname_memcache) { + // @todo fix memcache (name_id is not a property of ElggMetadata) $metabyname_memcache->delete("{$md->entity_guid}:{$md->name_id}"); } - //$name = sanitise_string(trim($name)); - //$value = sanitise_string(trim($value)); $value_type = detect_extender_valuetype($value, sanitise_string(trim($value_type))); $owner_guid = (int)$owner_guid; - if ($owner_guid==0) { - $owner_guid = get_loggedin_userid(); + if ($owner_guid == 0) { + $owner_guid = elgg_get_logged_in_user_guid(); } $access_id = (int)$access_id; - $access = get_access_sql_suffix(); - // Support boolean types (as integers) if (is_bool($value)) { - if ($value) { - $value = 1; - } else { - $value = 0; - } + $value = (int) $value; } // Add the metastring - $value = add_metastring($value); - if (!$value) { + $value_id = add_metastring($value); + if (!$value_id) { return false; } - $name = add_metastring($name); - if (!$name) { + $name_id = add_metastring($name); + if (!$name_id) { return false; } // If ok then add it - $result = update_data("UPDATE {$CONFIG->dbprefix}metadata set value_id='$value', value_type='$value_type', access_id=$access_id, owner_guid=$owner_guid where id=$id and name_id='$name'"); - if ($result!==false) { - $obj = get_metadata($id); - if (trigger_elgg_event('update', 'metadata', $obj)) { - return true; - } else { - delete_metadata($id); - } + $query = "UPDATE {$CONFIG->dbprefix}metadata" + . " set name_id='$name_id', value_id='$value_id', value_type='$value_type', access_id=$access_id," + . " owner_guid=$owner_guid where id=$id"; + + $result = update_data($query); + if ($result !== false) { + + elgg_get_metadata_cache()->save($md->entity_guid, $name, $value); + + // @todo this event tells you the metadata has been updated, but does not + // let you do anything about it. What is needed is a plugin hook before + // the update that passes old and new values. + $obj = elgg_get_metadata_from_id($id); + elgg_trigger_event('update', 'metadata', $obj); } return $result; @@ -343,16 +224,26 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i /** * This function creates metadata from an associative array of "key => value" pairs. * - * @param int $entity_guid - * @param string $name_and_values - * @param string $value_type - * @param int $owner_guid - * @param int $access_id - * @param bool $allow_multiple + * To achieve an array for a single key, pass in the same key multiple times with + * allow_multiple set to TRUE. This creates an indexed array. It does not support + * associative arrays and there is no guarantee on the ordering in the array. + * + * @param int $entity_guid The entity to attach the metadata to + * @param array $name_and_values Associative array - a value can be a string, number, bool + * @param string $value_type 'text', 'integer', or '' for automatic detection + * @param int $owner_guid GUID of entity that owns the metadata + * @param int $access_id Default is ACCESS_PRIVATE + * @param bool $allow_multiple Allow multiple values for one key. Default is FALSE + * + * @return bool */ -function create_metadata_from_array($entity_guid, array $name_and_values, $value_type, $owner_guid, $access_id = ACCESS_PRIVATE, $allow_multiple = false) { +function create_metadata_from_array($entity_guid, array $name_and_values, $value_type, $owner_guid, +$access_id = ACCESS_PRIVATE, $allow_multiple = false) { + foreach ($name_and_values as $k => $v) { - if (!create_metadata($entity_guid, $k, $v, $value_type, $owner_guid, $access_id, $allow_multiple)) { + $result = create_metadata($entity_guid, $k, $v, $value_type, $owner_guid, + $access_id, $allow_multiple); + if (!$result) { return false; } } @@ -360,236 +251,206 @@ function create_metadata_from_array($entity_guid, array $name_and_values, $value } /** - * Delete an item of metadata, where the current user has access. + * Returns metadata. Accepts all elgg_get_entities() options for entity + * restraints. * - * @param $id int The item of metadata to delete. + * @see elgg_get_entities + * + * @warning 1.7's find_metadata() didn't support limits and returned all metadata. + * This function defaults to a limit of 25. There is probably not a reason + * for you to return all metadata unless you're exporting an entity, + * have other restraints in place, or are doing something horribly + * wrong in your code. + * + * @param array $options Array in format: + * + * metadata_names => NULL|ARR metadata names + * metadata_values => NULL|ARR metadata values + * metadata_ids => NULL|ARR metadata ids + * metadata_case_sensitive => BOOL Overall Case sensitive + * metadata_owner_guids => NULL|ARR guids for metadata owners + * metadata_created_time_lower => INT Lower limit for created time. + * metadata_created_time_upper => INT Upper limit for created time. + * metadata_calculation => STR Perform the MySQL function on the metadata values returned. + * The "metadata_calculation" option causes this function to + * return the result of performing a mathematical calculation on + * all metadata that match the query instead of returning + * ElggMetadata objects. + * + * @return ElggMetadata[]|mixed + * @since 1.8.0 */ -function delete_metadata($id) { - global $CONFIG; - - $id = (int)$id; - $metadata = get_metadata($id); - - if ($metadata) { - // Tidy up if memcache is enabled. - static $metabyname_memcache; - if ((!$metabyname_memcache) && (is_memcache_available())) { - $metabyname_memcache = new ElggMemcache('metabyname_memcache'); - } +function elgg_get_metadata(array $options = array()) { - if ($metabyname_memcache) { - $metabyname_memcache->delete("{$metadata->entity_guid}:{$metadata->name_id}"); - } - - if (($metadata->canEdit()) && (trigger_elgg_event('delete', 'metadata', $metadata))) { - return delete_data("DELETE from {$CONFIG->dbprefix}metadata where id=$id"); - } + // @todo remove support for count shortcut - see #4393 + // support shortcut of 'count' => true for 'metadata_calculation' => 'count' + if (isset($options['count']) && $options['count']) { + $options['metadata_calculation'] = 'count'; + unset($options['count']); } - return false; + $options['metastring_type'] = 'metadata'; + return elgg_get_metastring_based_objects($options); } /** - * Return the metadata values that match your query. + * Deletes metadata based on $options. + * + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * This requires at least one constraint: metadata_owner_guid(s), + * metadata_name(s), metadata_value(s), or guid(s) must be set. * - * @param string $meta_name - * @return mixed either a value, an array of ElggMetadata or false. + * @param array $options An options array. {@see elgg_get_metadata()} + * @return bool|null true on success, false on failure, null if no metadata to delete. + * @since 1.8.0 */ -function get_metadata_byname($entity_guid, $meta_name) { - global $CONFIG; - - $meta_name = get_metastring_id($meta_name); - - if (empty($meta_name)) { - return false; - } - - $entity_guid = (int)$entity_guid; - $access = get_access_sql_suffix("e"); - $md_access = get_access_sql_suffix("m"); - - // If memcache is available then cache this (cache only by name for now since this is the most common query) - $meta = null; - static $metabyname_memcache; - if ((!$metabyname_memcache) && (is_memcache_available())) { - $metabyname_memcache = new ElggMemcache('metabyname_memcache'); - } - if ($metabyname_memcache) { - $meta = $metabyname_memcache->load("{$entity_guid}:{$meta_name}"); - } - if ($meta) { - return $meta; - } - - $result = get_data("SELECT m.*, n.string as name, v.string as value from {$CONFIG->dbprefix}metadata m JOIN {$CONFIG->dbprefix}entities e ON e.guid = m.entity_guid JOIN {$CONFIG->dbprefix}metastrings v on m.value_id = v.id JOIN {$CONFIG->dbprefix}metastrings n on m.name_id = n.id where m.entity_guid=$entity_guid and m.name_id='$meta_name' and $access and $md_access ORDER BY m.id ASC", "row_to_elggmetadata"); - if (!$result) { +function elgg_delete_metadata(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) { return false; } + $options['metastring_type'] = 'metadata'; + $result = elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); - // Cache if memcache available - if ($metabyname_memcache) { - if (count($result) == 1) { - $r = $result[0]; - } else { - $r = $result; - } - // This is a bit of a hack - we shorten the expiry on object - // metadata so that it'll be gone in an hour. This means that - // deletions and more importantly updates will filter through eventually. - $metabyname_memcache->setDefaultExpiry(3600); - $metabyname_memcache->save("{$entity_guid}:{$meta_name}", $r); - } - if (count($result) == 1) { - return $result[0]; - } + // This moved last in case an object's constructor sets metadata. Currently the batch + // delete process has to create the entity to delete its metadata. See #5214 + elgg_get_metadata_cache()->invalidateByOptions('delete', $options); return $result; } /** - * Return all the metadata for a given GUID. + * Disables metadata based on $options. * - * @param int $entity_guid + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return bool|null true on success, false on failure, null if no metadata disabled. + * @since 1.8.0 */ -function get_metadata_for_entity($entity_guid) { - global $CONFIG; +function elgg_disable_metadata(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) { + return false; + } - $entity_guid = (int)$entity_guid; - $access = get_access_sql_suffix("e"); - $md_access = get_access_sql_suffix("m"); + elgg_get_metadata_cache()->invalidateByOptions('disable', $options); + + // if we can see hidden (disabled) we need to use the offset + // otherwise we risk an infinite loop if there are more than 50 + $inc_offset = access_get_show_hidden_status(); - return get_data("SELECT m.*, n.string as name, v.string as value from {$CONFIG->dbprefix}metadata m JOIN {$CONFIG->dbprefix}entities e ON e.guid = m.entity_guid JOIN {$CONFIG->dbprefix}metastrings v on m.value_id = v.id JOIN {$CONFIG->dbprefix}metastrings n on m.name_id = n.id where m.entity_guid=$entity_guid and $access and $md_access", "row_to_elggmetadata"); + $options['metastring_type'] = 'metadata'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset); } /** - * Get the metadata where the entities they are referring to match a given criteria. - * - * @param mixed $meta_name - * @param mixed $meta_value - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $limit - * @param int $offset - * @param string $order_by Optional ordering. - * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. + * Enables metadata based on $options. + * + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * + * @warning In order to enable metadata, you must first use + * {@link access_show_hidden_entities()}. + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return bool|null true on success, false on failure, null if no metadata enabled. + * @since 1.8.0 */ -function find_metadata($meta_name = "", $meta_value = "", $entity_type = "", $entity_subtype = "", $limit = 10, $offset = 0, $order_by = "", $site_guid = 0) { - global $CONFIG; - - $meta_n = get_metastring_id($meta_name); - $meta_v = get_metastring_id($meta_value); - - $entity_type = sanitise_string($entity_type); - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - $limit = (int)$limit; - $offset = (int)$offset; - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - - $order_by = sanitise_string($order_by); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $where = array(); - - if ($entity_type!="") { - $where[] = "e.type='$entity_type'"; - } - - if ($entity_subtype) { - $where[] = "e.subtype=$entity_subtype"; - } - - if ($meta_name!="") { - if (!$meta_v) { - // The value is set, but we didn't get a value... so something went wrong. - return false; - } - $where[] = "m.name_id='$meta_n'"; - } - if ($meta_value!="") { - // The value is set, but we didn't get a value... so something went wrong. - if (!$meta_v) { - return false; - } - $where[] = "m.value_id='$meta_v'"; - } - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; +function elgg_enable_metadata(array $options) { + if (!$options || !is_array($options)) { + return false; } - $query = "SELECT m.*, n.string as name, v.string as value from {$CONFIG->dbprefix}entities e JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid JOIN {$CONFIG->dbprefix}metastrings v on m.value_id = v.id JOIN {$CONFIG->dbprefix}metastrings n on m.name_id = n.id where"; - foreach ($where as $w) { - $query .= " $w and "; - } - $query .= get_access_sql_suffix("e"); // Add access controls - $query .= ' and ' . get_access_sql_suffix("m"); // Add access controls - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + elgg_get_metadata_cache()->invalidateByOptions('enable', $options); - return get_data($query, "row_to_elggmetadata"); + $options['metastring_type'] = 'metadata'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback'); } - +/** + * ElggEntities interfaces + */ /** * Returns entities based upon metadata. Also accepts all * options available to elgg_get_entities(). Supports * the singular option shortcut. * + * @note Using metadata_names and metadata_values results in a + * "names IN (...) AND values IN (...)" clause. This is subtly + * differently than default multiple metadata_name_value_pairs, which use + * "(name = value) AND (name = value)" clauses. + * + * When in doubt, use name_value_pairs. + * + * To ask for entities that do not have a metadata value, use a custom + * where clause like this: + * + * $options['wheres'][] = "NOT EXISTS ( + * SELECT 1 FROM {$dbprefix}metadata md + * WHERE md.entity_guid = e.guid + * AND md.name_id = $name_metastring_id + * AND md.value_id = $value_metastring_id)"; + * + * Note the metadata name and value has been denormalized in the above example. + * * @see elgg_get_entities + * * @param array $options Array in format: * * metadata_names => NULL|ARR metadata names * * metadata_values => NULL|ARR metadata values * - * metadata_name_value_pairs => NULL|ARR (name = 'name', value => 'value', 'operand' => '=', 'case_sensitive' => TRUE) entries - * - * metadata_name_value_pairs_operator => NULL|STR The operator to use for combining (name = value) OPERATOR (name = value); default AND + * metadata_name_value_pairs => NULL|ARR ( + * name => 'name', + * value => 'value', + * 'operand' => '=', + * 'case_sensitive' => TRUE + * ) + * Currently if multiple values are sent via + * an array (value => array('value1', 'value2') + * the pair's operand will be forced to "IN". + * If passing "IN" as the operand and a string as the value, + * the value must be a properly quoted and escaped string. + * + * metadata_name_value_pairs_operator => NULL|STR The operator to use for combining + * (name = value) OPERATOR (name = value); default AND * * metadata_case_sensitive => BOOL Overall Case sensitive * - * @return array + * order_by_metadata => NULL|ARR array( + * 'name' => 'metadata_text1', + * 'direction' => ASC|DESC, + * 'as' => text|integer + * ) + * Also supports array('name' => 'metadata_text1') + * + * metadata_owner_guids => NULL|ARR guids for metadata owners + * + * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors. + * @since 1.7.0 */ function elgg_get_entities_from_metadata(array $options = array()) { $defaults = array( - 'metadata_names' => ELGG_ENTITIES_ANY_VALUE, - 'metadata_values' => ELGG_ENTITIES_ANY_VALUE, - 'metadata_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, + 'metadata_names' => ELGG_ENTITIES_ANY_VALUE, + 'metadata_values' => ELGG_ENTITIES_ANY_VALUE, + 'metadata_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, 'metadata_name_value_pairs_operator' => 'AND', - 'metadata_case_sensitive' => TRUE + 'metadata_case_sensitive' => TRUE, + 'order_by_metadata' => array(), + + 'metadata_owner_guids' => ELGG_ENTITIES_ANY_VALUE, ); $options = array_merge($defaults, $options); - $singulars = array('metadata_name', 'metadata_value', 'metadata_name_value_pair'); - $options = elgg_normalise_plural_options_array($options, $singulars); - - $clauses = elgg_get_entity_metadata_where_sql('e', $options['metadata_names'], $options['metadata_values'], - $options['metadata_name_value_pairs'], $options['metadata_name_value_pairs_operator'], $options['metadata_case_sensitive']); - - if ($clauses) { - // merge wheres to pass to get_entities() - if (isset($options['wheres']) && !is_array($options['wheres'])) { - $options['wheres'] = array($options['wheres']); - } elseif (!isset($options['wheres'])) { - $options['wheres'] = array(); - } - - $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']); + $singulars = array('metadata_name', 'metadata_value', + 'metadata_name_value_pair', 'metadata_owner_guid'); - // merge joins to pass to get_entities() - if (isset($options['joins']) && !is_array($options['joins'])) { - $options['joins'] = array($options['joins']); - } elseif (!isset($options['joins'])) { - $options['joins'] = array(); - } + $options = elgg_normalise_plural_options_array($options, $singulars); - $options['joins'] = array_merge($options['joins'], $clauses['joins']); + if (!$options = elgg_entities_get_metastrings_options('metadata', $options)) { + return FALSE; } return elgg_get_entities($options); @@ -597,47 +458,70 @@ function elgg_get_entities_from_metadata(array $options = array()) { /** * Returns metadata name and value SQL where for entities. - * nb: $names and $values are not paired. Use $pairs for this. + * NB: $names and $values are not paired. Use $pairs for this. * Pairs default to '=' operand. * - * @param $prefix - * @param ARR|NULL $names - * @param ARR|NULL $values - * @param ARR|NULL $pairs array of names / values / operands - * @param AND|OR $pair_operator Operator to use to join the where clauses for pairs - * @param BOOL $case_sensitive - * @return FALSE|array False on fail, array('joins', 'wheres') + * This function is reused for annotations because the tables are + * exactly the same. + * + * @param string $e_table Entities table name + * @param string $n_table Normalized metastrings table name (Where entities, + * values, and names are joined. annotations / metadata) + * @param array|null $names Array of names + * @param array|null $values Array of values + * @param array|null $pairs Array of names / values / operands + * @param string $pair_operator ("AND" or "OR") Operator to use to join the where clauses for pairs + * @param bool $case_sensitive Case sensitive metadata names? + * @param array|null $order_by_metadata Array of names / direction + * @param array|null $owner_guids Array of owner GUIDs + * + * @return false|array False on fail, array('joins', 'wheres') + * @since 1.7.0 + * @access private */ -function elgg_get_entity_metadata_where_sql($table, $names = NULL, $values = NULL, $pairs = NULL, $pair_operator = 'AND', $case_sensitive = TRUE) { +function elgg_get_entity_metadata_where_sql($e_table, $n_table, $names = NULL, $values = NULL, +$pairs = NULL, $pair_operator = 'AND', $case_sensitive = TRUE, $order_by_metadata = NULL, +$owner_guids = NULL) { + global $CONFIG; // short circuit if nothing requested // 0 is a valid (if not ill-conceived) metadata name. // 0 is also a valid metadata value for FALSE, NULL, or 0 + // 0 is also a valid(ish) owner_guid if ((!$names && $names !== 0) && (!$values && $values !== 0) - && (!$pairs && $pairs !== 0)) { + && (!$pairs && $pairs !== 0) + && (!$owner_guids && $owner_guids !== 0) + && !$order_by_metadata) { return ''; } + // join counter for incremental joins. + $i = 1; + // binary forces byte-to-byte comparision of strings, making // it case- and diacritical-mark- sensitive. // only supported on values. $binary = ($case_sensitive) ? ' BINARY ' : ''; - $access = get_access_sql_suffix('md'); + $access = get_access_sql_suffix('n_table'); $return = array ( 'joins' => array (), - 'wheres' => array() + 'wheres' => array(), + 'orders' => array() ); + // will always want to join these tables if pulling metastrings. + $return['joins'][] = "JOIN {$CONFIG->dbprefix}{$n_table} n_table on + {$e_table}.guid = n_table.entity_guid"; + $wheres = array(); // get names wheres and joins $names_where = ''; if ($names !== NULL) { - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metadata md on {$table}.guid = md.entity_guid"; if (!is_array($names)) { $names = array($names); } @@ -652,7 +536,7 @@ function elgg_get_entity_metadata_where_sql($table, $names = NULL, $values = NUL } if ($names_str = implode(',', $sanitised_names)) { - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msn on md.name_id = msn.id"; + $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msn on n_table.name_id = msn.id"; $names_where = "(msn.string IN ($names_str))"; } } @@ -660,8 +544,6 @@ function elgg_get_entity_metadata_where_sql($table, $names = NULL, $values = NUL // get values wheres and joins $values_where = ''; if ($values !== NULL) { - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metadata md on {$table}.guid = md.entity_guid"; - if (!is_array($values)) { $values = array($values); } @@ -676,7 +558,7 @@ function elgg_get_entity_metadata_where_sql($table, $names = NULL, $values = NUL } if ($values_str = implode(',', $sanitised_values)) { - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msv on md.value_id = msv.id"; + $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msv on n_table.value_id = msv.id"; $values_where = "({$binary}msv.string IN ($values_str))"; } } @@ -701,7 +583,7 @@ function elgg_get_entity_metadata_where_sql($table, $names = NULL, $values = NUL // @todo when the pairs are > 3 should probably split the query up to // denormalize the strings table. - $i = 1; + foreach ($pairs as $index => $pair) { // @todo move this elsewhere? // support shortcut 'n' => 'v' method. @@ -712,11 +594,6 @@ function elgg_get_entity_metadata_where_sql($table, $names = NULL, $values = NUL ); } - // @todo The multiple joins are only needed when the operator is AND - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metadata md{$i} on {$table}.guid = md{$i}.entity_guid"; - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msn{$i} on md{$i}.name_id = msn{$i}.id"; - $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msv{$i} on md{$i}.value_id = msv{$i}.id"; - // must have at least a name and value if (!isset($pair['name']) || !isset($pair['value'])) { // @todo should probably return false. @@ -732,279 +609,148 @@ function elgg_get_entity_metadata_where_sql($table, $names = NULL, $values = NUL } if (isset($pair['operand'])) { - $operand = mysql_real_escape_string($pair['operand']); + $operand = sanitise_string($pair['operand']); } else { $operand = ' = '; } + // for comparing + $trimmed_operand = trim(strtolower($operand)); + + $access = get_access_sql_suffix("n_table{$i}"); // if the value is an int, don't quote it because str '15' < str '5' // if the operand is IN don't quote it because quoting should be done already. - //$value = trim(strtolower($operand)) == 'in' ? $pair['value'] : "'{$pair['value']}'"; - if (trim(strtolower($operand)) == 'in' || is_numeric($pair['value'])) { + if (is_numeric($pair['value'])) { $value = sanitise_string($pair['value']); + } else if (is_bool($pair['value'])) { + $value = (int) $pair['value']; + } else if (is_array($pair['value'])) { + $values_array = array(); + + foreach ($pair['value'] as $pair_value) { + if (is_numeric($pair_value)) { + $values_array[] = sanitise_string($pair_value); + } else { + $values_array[] = "'" . sanitise_string($pair_value) . "'"; + } + } + + if ($values_array) { + $value = '(' . implode(', ', $values_array) . ')'; + } + + // @todo allow support for non IN operands with array of values. + // will have to do more silly joins. + $operand = 'IN'; + } else if ($trimmed_operand == 'in') { + $value = "({$pair['value']})"; } else { - $value = '\'' . sanitise_string($pair['value']) . '\''; + $value = "'" . sanitise_string($pair['value']) . "'"; } $name = sanitise_string($pair['name']); - $access = get_access_sql_suffix("md{$i}"); - $pair_wheres[] = "(msn{$i}.string = '$name' AND {$pair_binary}msv{$i}.string $operand $value AND $access)"; + // @todo The multiple joins are only needed when the operator is AND + $return['joins'][] = "JOIN {$CONFIG->dbprefix}{$n_table} n_table{$i} + on {$e_table}.guid = n_table{$i}.entity_guid"; + $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msn{$i} + on n_table{$i}.name_id = msn{$i}.id"; + $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msv{$i} + on n_table{$i}.value_id = msv{$i}.id"; + + $pair_wheres[] = "(msn{$i}.string = '$name' AND {$pair_binary}msv{$i}.string + $operand $value AND $access)"; + $i++; } - if ($where = implode (" $pair_operator ", $pair_wheres)) { + if ($where = implode(" $pair_operator ", $pair_wheres)) { $wheres[] = "($where)"; } } - if ($where = implode(' OR ', $wheres)) { - $return['wheres'][] = "($where)"; - } - - return $return; -} - -/** - * Return a list of entities based on the given search criteria. - * - * @deprecated 1.7 use elgg_get_entities_from_metadata(). - * @param mixed $meta_name - * @param mixed $meta_value - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $limit - * @param int $offset - * @param string $order_by Optional ordering. - * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. - * @param true|false $count If set to true, returns the total number of entities rather than a list. (Default: false) - * @param true|false $case_sensitive If set to false this searches for the meta data without case sensitivity. (Default: true) - * - * @return int|array A list of entities, or a count if $count is set to true - */ -function get_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", $entity_subtype = "", -$owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, -$count = FALSE, $case_sensitive = TRUE) { - - elgg_deprecated_notice('get_entities_from_metadata() was deprecated by elgg_get_entities_from_metadata()!', 1.7); - - $options = array(); - - $options['metadata_names'] = $meta_name; - - if ($meta_value) { - $options['metadata_values'] = $meta_value; - } - - if ($entity_type) { - $options['types'] = $entity_type; - } - - if ($entity_subtype) { - $options['subtypes'] = $entity_subtype; - } - - if ($owner_guid) { - $options['owner_guid'] = $owner_guid; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } + // add owner_guids + if ($owner_guids) { + if (is_array($owner_guids)) { + $sanitised = array_map('sanitise_int', $owner_guids); + $owner_str = implode(',', $sanitised); + } else { + $owner_str = sanitise_int($owner_guids); + } - if ($order_by) { - $options['order_by']; + $wheres[] = "(n_table.owner_guid IN ($owner_str))"; } - if ($site_guid) { - $options['site_guid']; + if ($where = implode(' AND ', $wheres)) { + $return['wheres'][] = "($where)"; } - if ($count) { - $options['count'] = $count; + if (is_array($order_by_metadata)) { + if ((count($order_by_metadata) > 0) && !isset($order_by_metadata[0])) { + // singleton, so fix + $order_by_metadata = array($order_by_metadata); + } + foreach ($order_by_metadata as $order_by) { + if (is_array($order_by) && isset($order_by['name'])) { + $name = sanitise_string($order_by['name']); + if (isset($order_by['direction'])) { + $direction = sanitise_string($order_by['direction']); + } else { + $direction = 'ASC'; + } + $return['joins'][] = "JOIN {$CONFIG->dbprefix}{$n_table} n_table{$i} + on {$e_table}.guid = n_table{$i}.entity_guid"; + $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msn{$i} + on n_table{$i}.name_id = msn{$i}.id"; + $return['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msv{$i} + on n_table{$i}.value_id = msv{$i}.id"; + + $access = get_access_sql_suffix("n_table{$i}"); + + $return['wheres'][] = "(msn{$i}.string = '$name' AND $access)"; + if (isset($order_by['as']) && $order_by['as'] == 'integer') { + $return['orders'][] = "CAST(msv{$i}.string AS SIGNED) $direction"; + } else { + $return['orders'][] = "msv{$i}.string $direction"; + } + $i++; + } + } } - // need to be able to pass false - $options['metadata_case_sensitive'] = $case_sensitive; - - return elgg_get_entities_from_metadata($options); + return $return; } /** - * Return a list of entities suitable for display based on the given search criteria. + * Returns a list of entities filtered by provided metadata. * - * @see elgg_view_entity_list + * @see elgg_get_entities_from_metadata * - * @param mixed $meta_name Metadata name to search on - * @param mixed $meta_value The value to match, optionally - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity - * @param int $limit Number of entities to display per page - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow users to toggle to the gallery view. Default: true - * @param true|false $pagination Display pagination? Default: true + * @param array $options Options array * - * @return string A list of entities suitable for display + * @return array + * @since 1.7.0 */ -function list_entities_from_metadata($meta_name, $meta_value = "", $entity_type = ELGG_ENTITIES_ANY_VALUE, $entity_subtype = ELGG_ENTITIES_ANY_VALUE, $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = true, $case_sensitive = true ) { - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $options = array( - 'metadata_name' => $meta_name, - 'metadata_value' => $meta_value, - 'types' => $entity_type, - 'subtypes' => $entity_subtype, - 'owner_guid' => $owner_guid, - 'limit' => $limit, - 'offset' => $offset, - 'count' => TRUE, - 'case_sensitive' => $case_sensitive - ); - $count = elgg_get_entities_from_metadata($options); - - $options['count'] = FALSE; - $entities = elgg_get_entities_from_metadata($options); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); +function elgg_list_entities_from_metadata($options) { + return elgg_list_entities($options, 'elgg_get_entities_from_metadata'); } /** - * @deprecated 1.7. Use elgg_get_entities_from_metadata(). - * @param $meta_array - * @param $entity_type - * @param $entity_subtype - * @param $owner_guid - * @param $limit - * @param $offset - * @param $order_by - * @param $site_guid - * @param $count - * @param $meta_array_operator - * @return unknown_type + * Other functions */ -function get_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", -$owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, -$count = false, $meta_array_operator = 'and') { - elgg_deprecated_notice('get_entities_from_metadata_multi() was deprecated by elgg_get_entities_from_metadata()!', 1.7); - - if (!is_array($meta_array) || sizeof($meta_array) == 0) { - return false; - } - - $options = array(); - - $options['metadata_name_value_pairs'] = $meta_array; - - if ($entity_type) { - $options['types'] = $entity_type; - } - - if ($entity_subtype) { - $options['subtypes'] = $entity_subtype; - } - - if ($owner_guid) { - $options['owner_guid'] = $owner_guid; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } - - if ($order_by) { - $options['order_by']; - } - - if ($site_guid) { - $options['site_guid']; - } - - if ($count) { - $options['count'] = $count; - } - - $options['metadata_name_value_pairs_operator'] = $meta_array_operator; - - return elgg_get_entities_from_metadata($options); -} /** - * Returns a viewable list of entities based on the given search criteria. - * - * @see elgg_view_entity_list - * - * @param array $meta_array Array of 'name' => 'value' pairs - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $limit - * @param int $offset - * @param string $order_by Optional ordering. - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow users to toggle to the gallery view. Default: true - * @param true|false $pagination Display pagination? Default: true - * @return string List of ElggEntities suitable for display - */ -function list_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = true) { - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", $site_guid, true); - $entities = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", $site_guid, false); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); -} - -/** - * Clear all the metadata for a given entity, assuming you have access to that metadata. + * Handler called by trigger_plugin_hook on the "export" event. * - * @param int $guid - */ -function clear_metadata($entity_guid) { - global $CONFIG; - - $entity_guid = (int)$entity_guid; - if ($entity = get_entity($entity_guid)) { - if ($entity->canEdit()) { - return delete_data("DELETE from {$CONFIG->dbprefix}metadata where entity_guid={$entity_guid}"); - } - } - return false; -} - -/** - * Clear all annotations belonging to a given owner_guid + * @param string $hook export + * @param string $entity_type all + * @param mixed $returnvalue Value returned from previous hook + * @param mixed $params Params * - * @param int $owner_guid The owner - */ -function clear_metadata_by_owner($owner_guid) { - global $CONFIG; - - $owner_guid = (int)$owner_guid; - - $metas = get_data("SELECT id from {$CONFIG->dbprefix}metadata WHERE owner_guid=$owner_guid"); - $deleted = 0; - - if (is_array($metas)) { - foreach ($metas as $id) { - // Is this the best way? - if (delete_metadata($id->id)) { - $deleted++; - } - } - } - - return $deleted; -} - -/** - * Handler called by trigger_plugin_hook on the "export" event. + * @return array + * @access private + * + * @throws InvalidParameterException */ function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params) { // Sanity check values @@ -1016,12 +762,13 @@ function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params) throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonArrayReturnValue')); } - $guid = (int)$params['guid']; - $name = $params['name']; - - $result = get_metadata_for_entity($guid); + $result = elgg_get_metadata(array( + 'guid' => (int)$params['guid'], + 'limit' => 0, + )); if ($result) { + /* @var ElggMetadata[] $result */ foreach ($result as $r) { $returnvalue[] = $r->export(); } @@ -1031,34 +778,37 @@ function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params) } /** - * Takes in a comma-separated string and returns an array of tags which have been trimmed and set to lower case + * Takes in a comma-separated string and returns an array of tags + * which have been trimmed * * @param string $string Comma-separated tag string + * * @return array|false An array of strings, or false on failure */ function string_to_tag_array($string) { if (is_string($string)) { - $ar = explode(",",$string); - $ar = array_map('trim', $ar); // trim blank spaces - $ar = array_map('elgg_strtolower', $ar); // make lower case : [Marcus Povey 20090605 - Using mb wrapper function using UTF8 safe function where available] - $ar = array_filter($ar, 'is_not_null'); // Remove null values + $ar = explode(",", $string); + $ar = array_map('trim', $ar); + $ar = array_filter($ar, 'is_not_null'); + $ar = array_map('strip_tags', $ar); return $ar; } return false; - } /** - * Takes a metadata array (which has all kinds of properties) and turns it into a simple array of strings + * Takes a metadata array (which has all kinds of properties) + * and turns it into a simple array of strings * * @param array $array Metadata array + * * @return array Array of strings */ function metadata_array_to_values($array) { $valuearray = array(); if (is_array($array)) { - foreach($array as $element) { + foreach ($array as $element) { $valuearray[] = $element->value; } } @@ -1067,14 +817,18 @@ function metadata_array_to_values($array) { } /** - * Get the URL for this item of metadata, by default this links to the export handler in the current view. + * Get the URL for this metadata + * + * By default this links to the export handler in the current view. * - * @param int $id + * @param int $id Metadata ID + * + * @return mixed */ function get_metadata_url($id) { $id = (int)$id; - if ($extender = get_metadata($id)) { + if ($extender = elgg_get_metadata_from_id($id)) { return get_extender_url($extender); } return false; @@ -1084,8 +838,10 @@ function get_metadata_url($id) { * Mark entities with a particular type and subtype as having access permissions * that can be changed independently from their parent entity * - * @param string $type The type - object, user, etc + * @param string $type The type - object, user, etc * @param string $subtype The subtype; all subtypes by default + * + * @return void */ function register_metadata_as_independent($type, $subtype = '*') { global $CONFIG; @@ -1099,9 +855,10 @@ function register_metadata_as_independent($type, $subtype = '*') { * Determines whether entities of a given type and subtype should not change * their metadata in line with their parent entity * - * @param string $type The type - object, user, etc + * @param string $type The type - object, user, etc * @param string $subtype The entity subtype - * @return true|false + * + * @return bool */ function is_metadata_independent($type, $subtype) { global $CONFIG; @@ -1118,17 +875,20 @@ function is_metadata_independent($type, $subtype) { /** * When an entity is updated, resets the access ID on all of its child metadata * - * @param string $event The name of the event - * @param string $object_type The type of object - * @param ElggEntity $object The entity itself + * @param string $event The name of the event + * @param string $object_type The type of object + * @param ElggEntity $object The entity itself + * + * @return true */ function metadata_update($event, $object_type, $object) { if ($object instanceof ElggEntity) { if (!is_metadata_independent($object->getType(), $object->getSubtype())) { - global $CONFIG; + $db_prefix = elgg_get_config('dbprefix'); $access_id = (int) $object->access_id; $guid = (int) $object->getGUID(); - update_data("update {$CONFIG->dbprefix}metadata set access_id = {$access_id} where entity_guid = {$guid}"); + $query = "update {$db_prefix}metadata set access_id = {$access_id} where entity_guid = {$guid}"; + update_data($query); } } return true; @@ -1137,22 +897,82 @@ function metadata_update($event, $object_type, $object) { /** * Register a metadata url handler. * - * @param string $function_name The function. * @param string $extender_name The name, default 'all'. + * @param string $function The function name. + * + * @return bool */ -function register_metadata_url_handler($function_name, $extender_name = "all") { - return register_extender_url_handler($function_name, 'metadata', $extender_name); +function elgg_register_metadata_url_handler($extender_name, $function) { + return elgg_register_extender_url_handler('metadata', $extender_name, $function); +} + +/** + * Get the global metadata cache instance + * + * @return ElggVolatileMetadataCache + * + * @access private + */ +function elgg_get_metadata_cache() { + global $CONFIG; + if (empty($CONFIG->local_metadata_cache)) { + $CONFIG->local_metadata_cache = new ElggVolatileMetadataCache(); + } + return $CONFIG->local_metadata_cache; +} + +/** + * Invalidate the metadata cache based on options passed to various *_metadata functions + * + * @param string $action Action performed on metadata. "delete", "disable", or "enable" + * @param array $options Options passed to elgg_(delete|disable|enable)_metadata + * @return void + */ +function elgg_invalidate_metadata_cache($action, array $options) { + // remove as little as possible, optimizing for common cases + $cache = elgg_get_metadata_cache(); + if (empty($options['guid'])) { + // safest to clear everything unless we want to make this even more complex :( + $cache->flush(); + } else { + if (empty($options['metadata_name'])) { + // safest to clear the whole entity + $cache->clear($options['guid']); + } else { + switch ($action) { + case 'delete': + $cache->markEmpty($options['guid'], $options['metadata_name']); + break; + default: + $cache->markUnknown($options['guid'], $options['metadata_name']); + } + } + } } /** Register the hook */ -register_plugin_hook("export", "all", "export_metadata_plugin_hook", 2); +elgg_register_plugin_hook_handler("export", "all", "export_metadata_plugin_hook", 2); + /** Call a function whenever an entity is updated **/ -register_elgg_event_handler('update','all','metadata_update'); +elgg_register_event_handler('update', 'all', 'metadata_update'); // unit testing -register_plugin_hook('unit_test', 'system', 'metadata_test'); +elgg_register_plugin_hook_handler('unit_test', 'system', 'metadata_test'); + +/** + * Metadata unit test + * + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of other tests + * @param mixed $params Params + * + * @return array + * @access private + */ function metadata_test($hook, $type, $value, $params) { global $CONFIG; - $value[] = $CONFIG->path . 'engine/tests/objects/metadata.php'; + $value[] = $CONFIG->path . 'engine/tests/api/metadata.php'; + $value[] = $CONFIG->path . 'engine/tests/api/metadata_cache.php'; return $value; } diff --git a/engine/lib/metastrings.php b/engine/lib/metastrings.php index 71f32a786..57d876c06 100644 --- a/engine/lib/metastrings.php +++ b/engine/lib/metastrings.php @@ -3,35 +3,39 @@ * Elgg metastrngs * Functions to manage object metastrings. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage DataModel.MetaStrings */ /** Cache metastrings for a page */ +global $METASTRINGS_CACHE; $METASTRINGS_CACHE = array(); /** Keep a record of strings we know don't exist */ +global $METASTRINGS_DEADNAME_CACHE; $METASTRINGS_DEADNAME_CACHE = array(); + + /** * Return the meta string id for a given tag, or false. * - * @param string $string The value (whatever that is) to be stored - * @param bool $case_sensitive Do we want to make the query case sensitive? If not there may be more than one result - * @return int|array|false meta string id, array of ids or false if none found + * @param string $string The value to store + * @param bool $case_sensitive Do we want to make the query case sensitive? + * If not there may be more than one result + * + * @return int|array|false meta string id, array of ids or false if none found */ function get_metastring_id($string, $case_sensitive = TRUE) { global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE; $string = sanitise_string($string); - - // caching doesn't work for case insensitive searches - if ($case_sensitive) { + + // caching doesn't work for case insensitive searches + if ($case_sensitive) { $result = array_search($string, $METASTRINGS_CACHE, true); - - if ($result!==false) { + + if ($result !== false) { elgg_log("** Returning id for string:$string from cache."); return $result; } @@ -40,7 +44,7 @@ function get_metastring_id($string, $case_sensitive = TRUE) { if (in_array($string, $METASTRINGS_DEADNAME_CACHE, true)) { return false; } - + // Experimental memcache $msfc = null; static $metastrings_memcache; @@ -63,19 +67,19 @@ function get_metastring_id($string, $case_sensitive = TRUE) { } $row = FALSE; - $metaStrings = get_data($query, "entity_row_to_elggstar"); + $metaStrings = get_data($query); if (is_array($metaStrings)) { if (sizeof($metaStrings) > 1) { $ids = array(); - foreach($metaStrings as $metaString) { + foreach ($metaStrings as $metaString) { $ids[] = $metaString->id; } return $ids; - } else { + } else if (isset($metaStrings[0])) { $row = $metaStrings[0]; } } - + if ($row) { $METASTRINGS_CACHE[$row->id] = $row->string; // Cache it @@ -98,6 +102,7 @@ function get_metastring_id($string, $case_sensitive = TRUE) { * When given an ID, returns the corresponding metastring * * @param int $id Metastring ID + * * @return string Metastring */ function get_metastring($id) { @@ -126,8 +131,9 @@ function get_metastring($id) { * Add a metastring. * It returns the id of the tag, whether by creating it or updating it. * - * @param string $string The value (whatever that is) to be stored - * @param bool $case_sensitive Do we want to make the query case sensitive? + * @param string $string The value (whatever that is) to be stored + * @param bool $case_sensitive Do we want to make the query case sensitive? + * * @return mixed Integer tag or false. */ function add_metastring($string, $case_sensitive = true) { @@ -154,6 +160,8 @@ function add_metastring($string, $case_sensitive = true) { /** * Delete any orphaned entries in metastrings. This is run by the garbage collector. * + * @return bool + * @access private */ function delete_orphaned_metastrings() { global $CONFIG; @@ -194,4 +202,702 @@ function delete_orphaned_metastrings() { )"; return delete_data($query); -}
\ No newline at end of file +} + +/** + * Returns an array of either ElggAnnotation or ElggMetadata objects. + * Accepts all elgg_get_entities() options for entity restraints. + * + * @see elgg_get_entities + * + * @param array $options Array in format: + * + * metastring_names => NULL|ARR metastring names + * + * metastring_values => NULL|ARR metastring values + * + * metastring_ids => NULL|ARR metastring ids + * + * metastring_case_sensitive => BOOL Overall Case sensitive + * + * metastring_owner_guids => NULL|ARR Guids for metadata owners + * + * metastring_created_time_lower => INT Lower limit for created time. + * + * metastring_created_time_upper => INT Upper limit for created time. + * + * metastring_calculation => STR Perform the MySQL function on the metastring values + * returned. + * This differs from egef_annotation_calculation in that + * it returns only the calculation of all annotation values. + * You can sum, avg, count, etc. egef_annotation_calculation() + * returns ElggEntities ordered by a calculation on their + * annotation values. + * + * metastring_type => STR metadata or annotation(s) + * + * @return mixed + * @access private + */ +function elgg_get_metastring_based_objects($options) { + $options = elgg_normalize_metastrings_options($options); + + switch ($options['metastring_type']) { + case 'metadata': + $type = 'metadata'; + $callback = 'row_to_elggmetadata'; + break; + + case 'annotations': + case 'annotation': + $type = 'annotations'; + $callback = 'row_to_elggannotation'; + break; + + default: + return false; + } + + $defaults = array( + // entities + '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' => get_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, + + // options are normalized to the plural in case we ever add support for them. + 'metastring_names' => ELGG_ENTITIES_ANY_VALUE, + 'metastring_values' => ELGG_ENTITIES_ANY_VALUE, + //'metastring_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, + //'metastring_name_value_pairs_operator' => 'AND', + + 'metastring_case_sensitive' => TRUE, + //'order_by_metastring' => array(), + 'metastring_calculation' => ELGG_ENTITIES_NO_VALUE, + + 'metastring_created_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'metastring_created_time_upper' => ELGG_ENTITIES_ANY_VALUE, + + 'metastring_owner_guids' => ELGG_ENTITIES_ANY_VALUE, + + 'metastring_ids' => ELGG_ENTITIES_ANY_VALUE, + + // sql + 'order_by' => 'n_table.time_created asc', + 'limit' => 10, + 'offset' => 0, + 'count' => FALSE, + 'selects' => array(), + 'wheres' => array(), + 'joins' => array(), + + 'callback' => $callback + ); + + // @todo Ignore site_guid right now because of #2910 + $options['site_guid'] = ELGG_ENTITIES_ANY_VALUE; + + $options = array_merge($defaults, $options); + + // 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', 'type_subtype_pair', + 'guid', 'owner_guid', 'container_guid', 'site_guid', + 'metastring_name', 'metastring_value', + 'metastring_owner_guid', 'metastring_id', + 'select', 'where', 'join' + ); + + $options = elgg_normalise_plural_options_array($options, $singulars); + + if (!$options) { + return false; + } + + $db_prefix = elgg_get_config('dbprefix'); + + // evaluate where clauses + if (!is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } + + $wheres = $options['wheres']; + + // entities + $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']); + + + $wheres[] = elgg_get_entity_time_where_sql('n_table', $options['metastring_created_time_upper'], + $options['metastring_created_time_lower'], null, null); + + $wheres[] = elgg_get_guid_based_where_sql('n_table.owner_guid', + $options['metastring_owner_guids']); + + // see if any functions failed + // remove empty strings on successful functions + foreach ($wheres as $i => $where) { + if ($where === FALSE) { + return FALSE; + } elseif (empty($where)) { + unset($wheres[$i]); + } + } + + // remove identical where clauses + $wheres = array_unique($wheres); + + // evaluate join clauses + if (!is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } + + $joins = $options['joins']; + $joins[] = "JOIN {$db_prefix}entities e ON n_table.entity_guid = e.guid"; + + // evaluate selects + if (!is_array($options['selects'])) { + $options['selects'] = array($options['selects']); + } + + $selects = $options['selects']; + + // For performance reasons we don't want the joins required for metadata / annotations + // unless we're going through one of their callbacks. + // this means we expect the functions passing different callbacks to pass their required joins. + // If we're doing a calculation + $custom_callback = ($options['callback'] == 'row_to_elggmetadata' + || $options['callback'] == 'row_to_elggannotation'); + $is_calculation = $options['metastring_calculation'] ? true : false; + + if ($custom_callback || $is_calculation) { + $joins[] = "JOIN {$db_prefix}metastrings n on n_table.name_id = n.id"; + $joins[] = "JOIN {$db_prefix}metastrings v on n_table.value_id = v.id"; + + $selects[] = 'n.string as name'; + $selects[] = 'v.string as value'; + } + + foreach ($joins as $i => $join) { + if ($join === FALSE) { + return FALSE; + } elseif (empty($join)) { + unset($joins[$i]); + } + } + + // metastrings + $metastring_clauses = elgg_get_metastring_sql('n_table', $options['metastring_names'], + $options['metastring_values'], null, $options['metastring_ids'], + $options['metastring_case_sensitive']); + + if ($metastring_clauses) { + $wheres = array_merge($wheres, $metastring_clauses['wheres']); + $joins = array_merge($joins, $metastring_clauses['joins']); + } else { + $wheres[] = get_access_sql_suffix('n_table'); + } + + if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) { + $selects = array_unique($selects); + // evalutate selects + $select_str = ''; + if ($selects) { + foreach ($selects as $select) { + $select_str .= ", $select"; + } + } + + $query = "SELECT DISTINCT n_table.*{$select_str} FROM {$db_prefix}$type n_table"; + } elseif ($options['count']) { + // count is over the entities + $query = "SELECT count(DISTINCT e.guid) as calculation FROM {$db_prefix}$type n_table"; + } else { + $query = "SELECT {$options['metastring_calculation']}(v.string) as calculation FROM {$db_prefix}$type n_table"; + } + + // remove identical join clauses + $joins = array_unique($joins); + + // add joins + foreach ($joins as $j) { + $query .= " $j "; + } + + // add wheres + $query .= ' WHERE '; + + foreach ($wheres as $w) { + $query .= " $w AND "; + } + + // Add access controls + $query .= get_access_sql_suffix('e'); + + // reverse order by + if (isset($options['reverse_order_by']) && $options['reverse_order_by']) { + $options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by'], + $defaults['order_by']); + } + + if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) { + if (isset($options['group_by'])) { + $options['group_by'] = sanitise_string($options['group_by']); + $query .= " GROUP BY {$options['group_by']}"; + } + + if (isset($options['order_by']) && $options['order_by']) { + $options['order_by'] = sanitise_string($options['order_by']); + $query .= " ORDER BY {$options['order_by']}, n_table.id"; + } + + if ($options['limit']) { + $limit = sanitise_int($options['limit']); + $offset = sanitise_int($options['offset'], false); + $query .= " LIMIT $offset, $limit"; + } + + $dt = get_data($query, $options['callback']); + return $dt; + } else { + $result = get_data_row($query); + return $result->calculation; + } +} + +/** + * Returns an array of joins and wheres for use in metastrings. + * + * @note The $pairs is reserved for name/value pairs if we want to implement those. + * + * @param string $table The annotation or metadata table name or alias + * @param array $names An array of names + * @param array $values An array of values + * @param array $pairs Name / value pairs. Not currently used. + * @param array $ids Metastring IDs + * @param bool $case_sensitive Should name and values be case sensitive? + * + * @return array + * @access private + */ +function elgg_get_metastring_sql($table, $names = null, $values = null, + $pairs = null, $ids = null, $case_sensitive = false) { + + if ((!$names && $names !== 0) + && (!$values && $values !== 0) + && !$ids + && (!$pairs && $pairs !== 0)) { + + return array(); + } + + $db_prefix = elgg_get_config('dbprefix'); + + // binary forces byte-to-byte comparision of strings, making + // it case- and diacritical-mark- sensitive. + // only supported on values. + $binary = ($case_sensitive) ? ' BINARY ' : ''; + + $return = array ( + 'joins' => array (), + 'wheres' => array() + ); + + $wheres = array(); + + // get names wheres and joins + $names_where = ''; + if ($names !== NULL) { + if (!is_array($names)) { + $names = array($names); + } + + $sanitised_names = array(); + foreach ($names as $name) { + // normalise to 0. + if (!$name) { + $name = '0'; + } + $sanitised_names[] = '\'' . sanitise_string($name) . '\''; + } + + if ($names_str = implode(',', $sanitised_names)) { + $return['joins'][] = "JOIN {$db_prefix}metastrings msn on $table.name_id = msn.id"; + $names_where = "(msn.string IN ($names_str))"; + } + } + + // get values wheres and joins + $values_where = ''; + if ($values !== NULL) { + if (!is_array($values)) { + $values = array($values); + } + + $sanitised_values = array(); + foreach ($values as $value) { + // normalize to 0 + if (!$value) { + $value = 0; + } + $sanitised_values[] = '\'' . sanitise_string($value) . '\''; + } + + if ($values_str = implode(',', $sanitised_values)) { + $return['joins'][] = "JOIN {$db_prefix}metastrings msv on $table.value_id = msv.id"; + $values_where = "({$binary}msv.string IN ($values_str))"; + } + } + + if ($ids !== NULL) { + if (!is_array($ids)) { + $ids = array($ids); + } + + $ids_str = implode(',', $ids); + + if ($ids_str) { + $wheres[] = "n_table.id IN ($ids_str)"; + } + } + + if ($names_where && $values_where) { + $wheres[] = "($names_where AND $values_where)"; + } elseif ($names_where) { + $wheres[] = $names_where; + } elseif ($values_where) { + $wheres[] = $values_where; + } + + $wheres[] = get_access_sql_suffix($table); + + if ($where = implode(' AND ', $wheres)) { + $return['wheres'][] = "($where)"; + } + + return $return; +} + +/** + * Normalizes metadata / annotation option names to their corresponding metastrings name. + * + * @param array $options An options array + * @since 1.8.0 + * @return array + * @access private + */ +function elgg_normalize_metastrings_options(array $options = array()) { + + // support either metastrings_type or metastring_type + // because I've made this mistake many times and hunting it down is a pain... + $type = elgg_extract('metastring_type', $options, null); + $type = elgg_extract('metastrings_type', $options, $type); + + $options['metastring_type'] = $type; + + // support annotation_ and annotations_ because they're way too easy to confuse + $prefixes = array('metadata_', 'annotation_', 'annotations_'); + + // map the metadata_* options to metastring_* options + $map = array( + 'names' => 'metastring_names', + 'values' => 'metastring_values', + 'case_sensitive' => 'metastring_case_sensitive', + 'owner_guids' => 'metastring_owner_guids', + 'created_time_lower' => 'metastring_created_time_lower', + 'created_time_upper' => 'metastring_created_time_upper', + 'calculation' => 'metastring_calculation', + 'ids' => 'metastring_ids' + ); + + foreach ($prefixes as $prefix) { + $singulars = array("{$prefix}name", "{$prefix}value", "{$prefix}owner_guid", "{$prefix}id"); + $options = elgg_normalise_plural_options_array($options, $singulars); + + foreach ($map as $specific => $normalized) { + $key = $prefix . $specific; + if (isset($options[$key])) { + $options[$normalized] = $options[$key]; + } + } + } + + return $options; +} + +/** + * Enables or disables a metastrings-based object by its id. + * + * @warning To enable disabled metastrings you must first use + * {@link access_show_hidden_entities()}. + * + * @param int $id The object's ID + * @param string $enabled Value to set to: yes or no + * @param string $type The type of table to use: metadata or annotations + * + * @return bool + * @throws InvalidParameterException + * @since 1.8.0 + * @access private + */ +function elgg_set_metastring_based_object_enabled_by_id($id, $enabled, $type) { + $id = (int)$id; + $db_prefix = elgg_get_config('dbprefix'); + + $object = elgg_get_metastring_based_object_from_id($id, $type); + + switch($type) { + case 'annotation': + case 'annotations': + $table = "{$db_prefix}annotations"; + break; + + case 'metadata': + $table = "{$db_prefix}metadata"; + break; + } + + if ($enabled === 'yes' || $enabled === 1 || $enabled === true) { + $enabled = 'yes'; + $event = 'enable'; + } elseif ($enabled === 'no' || $enabled === 0 || $enabled === false) { + $enabled = 'no'; + $event = 'disable'; + } else { + return false; + } + + $return = false; + + if ($object) { + // don't set it if it's already set. + if ($object->enabled == $enabled) { + $return = false; + } elseif ($object->canEdit() && (elgg_trigger_event($event, $type, $object))) { + $return = update_data("UPDATE $table SET enabled = '$enabled' where id = $id"); + } + } + + return $return; +} + +/** + * Runs metastrings-based objects found using $options through $callback + * + * @warning Unlike elgg_get_metastring_based_objects() this will not accept an + * empty options array! + * + * @warning This returns null on no ops. + * + * @param array $options An options array. {@See elgg_get_metastring_based_objects()} + * @param string $callback The callback to pass each result through + * @param bool $inc_offset Increment the offset? Pass false for callbacks that delete / disable + * + * @return bool|null true on success, false on failure, null if no objects are found. + * @since 1.8.0 + * @access private + */ +function elgg_batch_metastring_based_objects(array $options, $callback, $inc_offset = true) { + if (!$options || !is_array($options)) { + return false; + } + + $batch = new ElggBatch('elgg_get_metastring_based_objects', $options, $callback, 50, $inc_offset); + return $batch->callbackResult; +} + +/** + * Returns a singular metastring-based object by its ID. + * + * @param int $id The metastring-based object's ID + * @param string $type The type: annotation or metadata + * @return ElggMetadata|ElggAnnotation + * + * @since 1.8.0 + * @access private + */ +function elgg_get_metastring_based_object_from_id($id, $type) { + $id = (int)$id; + if (!$id) { + return false; + } + + $options = array( + 'metastring_type' => $type, + 'metastring_id' => $id + ); + + $obj = elgg_get_metastring_based_objects($options); + + if ($obj && count($obj) == 1) { + return $obj[0]; + } + + return false; +} + +/** + * Deletes a metastring-based object by its id + * + * @param int $id The object's ID + * @param string $type The object's metastring type: annotation or metadata + * @return bool + * + * @since 1.8.0 + * @access private + */ +function elgg_delete_metastring_based_object_by_id($id, $type) { + $id = (int)$id; + $db_prefix = elgg_get_config('dbprefix'); + + switch ($type) { + case 'annotation': + case 'annotations': + $type = 'annotations'; + break; + + case 'metadata': + $type = 'metadata'; + break; + + default: + return false; + } + + $obj = elgg_get_metastring_based_object_from_id($id, $type); + $table = $db_prefix . $type; + + if ($obj) { + // Tidy up if memcache is enabled. + // @todo only metadata is supported + if ($type == 'metadata') { + static $metabyname_memcache; + if ((!$metabyname_memcache) && (is_memcache_available())) { + $metabyname_memcache = new ElggMemcache('metabyname_memcache'); + } + + if ($metabyname_memcache) { + // @todo why name_id? is that even populated? + $metabyname_memcache->delete("{$obj->entity_guid}:{$obj->name_id}"); + } + } + + if (($obj->canEdit()) && (elgg_trigger_event('delete', $type, $obj))) { + return (bool)delete_data("DELETE from $table where id=$id"); + } + } + + return false; +} + +/** + * Entities interface helpers + */ + +/** + * Returns options to pass to elgg_get_entities() for metastrings operations. + * + * @param string $type Metastring type: annotations or metadata + * @param array $options Options + * + * @return array + * @since 1.7.0 + * @access private + */ +function elgg_entities_get_metastrings_options($type, $options) { + $valid_types = array('metadata', 'annotation'); + if (!in_array($type, $valid_types)) { + return FALSE; + } + + // the options for annotations are singular (annotation_name) but the table + // is plural (elgg_annotations) so rewrite for the table name. + $n_table = ($type == 'annotation') ? 'annotations' : $type; + + $singulars = array("{$type}_name", "{$type}_value", + "{$type}_name_value_pair", "{$type}_owner_guid"); + $options = elgg_normalise_plural_options_array($options, $singulars); + + $clauses = elgg_get_entity_metadata_where_sql('e', $n_table, $options["{$type}_names"], + $options["{$type}_values"], $options["{$type}_name_value_pairs"], + $options["{$type}_name_value_pairs_operator"], $options["{$type}_case_sensitive"], + $options["order_by_{$type}"], $options["{$type}_owner_guids"]); + + if ($clauses) { + // merge wheres to pass to get_entities() + if (isset($options['wheres']) && !is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } elseif (!isset($options['wheres'])) { + $options['wheres'] = array(); + } + + $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']); + + // merge joins to pass to get_entities() + if (isset($options['joins']) && !is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } elseif (!isset($options['joins'])) { + $options['joins'] = array(); + } + + $options['joins'] = array_merge($options['joins'], $clauses['joins']); + + if ($clauses['orders']) { + $order_by_metadata = implode(", ", $clauses['orders']); + if (isset($options['order_by']) && $options['order_by']) { + $options['order_by'] = "$order_by_metadata, {$options['order_by']}"; + } else { + $options['order_by'] = "$order_by_metadata, e.time_created DESC"; + } + } + } + + return $options; +} + +// unit testing +elgg_register_plugin_hook_handler('unit_test', 'system', 'metastrings_test'); + +/** + * Metadata unit test + * + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of other tests + * @param mixed $params Params + * + * @return array + * @access private + */ +function metastrings_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/metastrings.php'; + return $value; +} diff --git a/engine/lib/navigation.php b/engine/lib/navigation.php new file mode 100644 index 000000000..ab9cc05e8 --- /dev/null +++ b/engine/lib/navigation.php @@ -0,0 +1,527 @@ +<?php +/** + * Elgg navigation library + * Functions for managing menus and other navigational elements + * + * Breadcrumbs + * Elgg uses a breadcrumb stack. The page handlers (controllers in MVC terms) + * push the breadcrumb links onto the stack. @see elgg_push_breadcrumb() + * + * + * Pagination + * Automatically handled by Elgg when using elgg_list_entities* functions. + * @see elgg_list_entities() + * + * + * Tabs + * @see navigation/tabs view + * + * + * Menus + * Elgg uses a single interface to manage its menus. Menu items are added with + * {@link elgg_register_menu_item()}. This is generally used for menus that + * appear only once per page. For dynamic menus (such as the hover + * menu for user's avatar), a plugin hook is emitted when the menu is being + * created. The hook is 'register', 'menu:<menu_name>'. For more details on this, + * @see elgg_view_menu(). + * + * Menus supported by the Elgg core + * Standard menus: + * site Site navigation shown on every page. + * page Page menu usually shown in a sidebar. Uses Elgg's context. + * topbar Topbar menu shown on every page. The default has two sections. + * footer Like the topbar but in the footer. + * extras Links about content on the page. The RSS link is added to this. + * + * Dynamic menus (also called just-in-time menus): + * user_hover Avatar hover menu. The user entity is passed as a parameter. + * entity The set of links shown in the summary of an entity. + * river Links shown on river items. + * owner_block Links shown for a user or group in their owner block. + * filter The tab filter for content (all, mine, friends) + * title The buttons shown next to a content title. + * long-text The links shown above the input/longtext view. + * + * @package Elgg.Core + * @subpackage Navigation + */ + +/** + * Register an item for an Elgg menu + * + * @warning Generally you should not use this in response to the plugin hook: + * 'register', 'menu:<menu_name>'. If you do, you may end up with many incorrect + * links on a dynamic menu. + * + * @warning A menu item's name must be unique per menu. If more than one menu + * item with the same name are registered, the last menu item takes priority. + * + * @see elgg_view_menu() for the plugin hooks available for modifying a menu as + * it is being rendered. + * + * @param string $menu_name The name of the menu: site, page, userhover, + * userprofile, groupprofile, or any custom menu + * @param mixed $menu_item A ElggMenuItem object or an array of options in format: + * name => STR Menu item identifier (required) + * text => STR Menu item display text (required) + * href => STR Menu item URL (required) (false for non-links. + * @warning If you disable the href the <a> tag will + * not appear, so the link_class will not apply. If you + * put <a> tags in manually through the 'text' option + * the default CSS selector .elgg-menu-$menu > li > a + * may affect formatting. Wrap in a <span> if it does.) + * contexts => ARR Page context strings + * section => STR Menu section identifier + * title => STR Menu item tooltip + * selected => BOOL Is this menu item currently selected + * parent_name => STR Identifier of the parent menu item + * link_class => STR A class or classes for the <a> tag + * item_class => STR A class or classes for the <li> tag + * + * Additional options that the view output/url takes can be + * passed in the array. If the 'confirm' key is passed, the + * menu link uses the 'output/confirmlink' view. Custom + * options can be added by using the 'data' key with the + * value being an associative array. + * + * @return bool + * @since 1.8.0 + */ +function elgg_register_menu_item($menu_name, $menu_item) { + global $CONFIG; + + if (!isset($CONFIG->menus[$menu_name])) { + $CONFIG->menus[$menu_name] = array(); + } + + if (is_array($menu_item)) { + $item = ElggMenuItem::factory($menu_item); + if (!$item) { + elgg_log("Unable to add menu item '{$menu_item['name']}' to '$menu_name' menu", 'WARNING'); + elgg_log(print_r($menu_item, true), 'DEBUG'); + return false; + } + } else { + $item = $menu_item; + } + + $CONFIG->menus[$menu_name][] = $item; + return true; +} + +/** + * Remove an item from a menu + * + * @param string $menu_name The name of the menu + * @param string $item_name The unique identifier for this menu item + * + * @return bool + * @since 1.8.0 + */ +function elgg_unregister_menu_item($menu_name, $item_name) { + global $CONFIG; + + if (!isset($CONFIG->menus[$menu_name])) { + return false; + } + + foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) { + /* @var ElggMenuItem $menu_object */ + if ($menu_object->getName() == $item_name) { + unset($CONFIG->menus[$menu_name][$index]); + return true; + } + } + + return false; +} + +/** + * Check if a menu item has been registered + * + * @param string $menu_name The name of the menu + * @param string $item_name The unique identifier for this menu item + * + * @return bool + * @since 1.8.0 + */ +function elgg_is_menu_item_registered($menu_name, $item_name) { + global $CONFIG; + + if (!isset($CONFIG->menus[$menu_name])) { + return false; + } + + foreach ($CONFIG->menus[$menu_name] as $menu_object) { + /* @var ElggMenuItem $menu_object */ + if ($menu_object->getName() == $item_name) { + return true; + } + } + + return false; +} + +/** + * Convenience function for registering a button to title menu + * + * The URL must be $handler/$name/$guid where $guid is the guid of the page owner. + * The label of the button is "$handler:$name" so that must be defined in a + * language file. + * + * This is used primarily to support adding an add content button + * + * @param string $handler The handler to use or null to autodetect from context + * @param string $name Name of the button + * @return void + * @since 1.8.0 + */ +function elgg_register_title_button($handler = null, $name = 'add') { + if (elgg_is_logged_in()) { + + if (!$handler) { + $handler = elgg_get_context(); + } + + $owner = elgg_get_page_owner_entity(); + if (!$owner) { + // no owns the page so this is probably an all site list page + $owner = elgg_get_logged_in_user_entity(); + } + if ($owner && $owner->canWriteToContainer()) { + $guid = $owner->getGUID(); + elgg_register_menu_item('title', array( + 'name' => $name, + 'href' => "$handler/$name/$guid", + 'text' => elgg_echo("$handler:$name"), + 'link_class' => 'elgg-button elgg-button-action', + )); + } + } +} + +/** + * Adds a breadcrumb to the breadcrumbs stack. + * + * @param string $title The title to display + * @param string $link Optional. The link for the title. + * + * @return void + * @since 1.8.0 + * + * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs + */ +function elgg_push_breadcrumb($title, $link = NULL) { + global $CONFIG; + if (!isset($CONFIG->breadcrumbs)) { + $CONFIG->breadcrumbs = array(); + } + + // avoid key collisions. + $CONFIG->breadcrumbs[] = array('title' => elgg_get_excerpt($title, 100), 'link' => $link); +} + +/** + * Removes last breadcrumb entry. + * + * @return array popped item. + * @since 1.8.0 + * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs + */ +function elgg_pop_breadcrumb() { + global $CONFIG; + + if (is_array($CONFIG->breadcrumbs)) { + return array_pop($CONFIG->breadcrumbs); + } + + return FALSE; +} + +/** + * Returns all breadcrumbs as an array of array('title' => 'Readable Title', 'link' => 'URL') + * + * @return array Breadcrumbs + * @since 1.8.0 + * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs + */ +function elgg_get_breadcrumbs() { + global $CONFIG; + + if (isset($CONFIG->breadcrumbs) && is_array($CONFIG->breadcrumbs)) { + return $CONFIG->breadcrumbs; + } + + return array(); +} + +/** + * Set up the site menu + * + * Handles default, featured, and custom menu items + * + * @param string $hook + * @param string $type + * @param array $return Menu array + * @param array $params + * @return array + * @access private + */ +function elgg_site_menu_setup($hook, $type, $return, $params) { + + $featured_menu_names = elgg_get_config('site_featured_menu_names'); + $custom_menu_items = elgg_get_config('site_custom_menu_items'); + if ($featured_menu_names || $custom_menu_items) { + // we have featured or custom menu items + + $registered = $return['default']; + + // set up featured menu items + $featured = array(); + foreach ($featured_menu_names as $name) { + foreach ($registered as $index => $item) { + if ($item->getName() == $name) { + $featured[] = $item; + unset($registered[$index]); + } + } + } + + // add custom menu items + $n = 1; + foreach ($custom_menu_items as $title => $url) { + $item = new ElggMenuItem("custom$n", $title, $url); + $featured[] = $item; + $n++; + } + + $return['default'] = $featured; + if (count($registered) > 0) { + $return['more'] = $registered; + } + } else { + // no featured menu items set + $max_display_items = 5; + + // the first n are shown, rest added to more list + // if only one item on more menu, stick it with the rest + $num_menu_items = count($return['default']); + if ($num_menu_items > ($max_display_items + 1)) { + $return['more'] = array_splice($return['default'], $max_display_items); + } + } + + // check if we have anything selected + $selected = false; + foreach ($return as $section) { + foreach ($section as $item) { + if ($item->getSelected()) { + $selected = true; + break 2; + } + } + } + + if (!$selected) { + // nothing selected, match name to context or match url + $current_url = current_page_url(); + foreach ($return as $section_name => $section) { + foreach ($section as $key => $item) { + // only highlight internal links + if (strpos($item->getHref(), elgg_get_site_url()) === 0) { + if ($item->getName() == elgg_get_context()) { + $return[$section_name][$key]->setSelected(true); + break 2; + } + if ($item->getHref() == $current_url) { + $return[$section_name][$key]->setSelected(true); + break 2; + } + } + } + } + } + + return $return; +} + +/** + * Add the comment and like links to river actions menu + * @access private + */ +function elgg_river_menu_setup($hook, $type, $return, $params) { + if (elgg_is_logged_in()) { + $item = $params['item']; + /* @var ElggRiverItem $item */ + $object = $item->getObjectEntity(); + // comments and non-objects cannot be commented on or liked + if (!elgg_in_context('widgets') && $item->annotation_id == 0) { + // comments + if ($object->canComment()) { + $options = array( + 'name' => 'comment', + 'href' => "#comments-add-$object->guid", + 'text' => elgg_view_icon('speech-bubble'), + 'title' => elgg_echo('comment:this'), + 'rel' => 'toggle', + 'priority' => 50, + ); + $return[] = ElggMenuItem::factory($options); + } + } + + if (elgg_is_admin_logged_in()) { + $options = array( + 'name' => 'delete', + 'href' => elgg_add_action_tokens_to_url("action/river/delete?id=$item->id"), + 'text' => elgg_view_icon('delete'), + 'title' => elgg_echo('delete'), + 'confirm' => elgg_echo('deleteconfirm'), + 'priority' => 200, + ); + $return[] = ElggMenuItem::factory($options); + } + } + + return $return; +} + +/** + * Entity menu is list of links and info on any entity + * @access private + */ +function elgg_entity_menu_setup($hook, $type, $return, $params) { + if (elgg_in_context('widgets')) { + return $return; + } + + $entity = $params['entity']; + /* @var ElggEntity $entity */ + $handler = elgg_extract('handler', $params, false); + + // access + $access = elgg_view('output/access', array('entity' => $entity)); + $options = array( + 'name' => 'access', + 'text' => $access, + 'href' => false, + 'priority' => 100, + ); + $return[] = ElggMenuItem::factory($options); + + if ($entity->canEdit() && $handler) { + // edit link + $options = array( + 'name' => 'edit', + 'text' => elgg_echo('edit'), + 'title' => elgg_echo('edit:this'), + 'href' => "$handler/edit/{$entity->getGUID()}", + 'priority' => 200, + ); + $return[] = ElggMenuItem::factory($options); + + // delete link + $options = array( + 'name' => 'delete', + 'text' => elgg_view_icon('delete'), + 'title' => elgg_echo('delete:this'), + 'href' => "action/$handler/delete?guid={$entity->getGUID()}", + 'confirm' => elgg_echo('deleteconfirm'), + 'priority' => 300, + ); + $return[] = ElggMenuItem::factory($options); + } + + return $return; +} + +/** + * Widget menu is a set of widget controls + * @access private + */ +function elgg_widget_menu_setup($hook, $type, $return, $params) { + + $widget = $params['entity']; + /* @var ElggWidget $widget */ + $show_edit = elgg_extract('show_edit', $params, true); + + $collapse = array( + 'name' => 'collapse', + 'text' => ' ', + 'href' => "#elgg-widget-content-$widget->guid", + 'class' => 'elgg-widget-collapse-button', + 'rel' => 'toggle', + 'priority' => 1 + ); + $return[] = ElggMenuItem::factory($collapse); + + if ($widget->canEdit()) { + $delete = array( + 'name' => 'delete', + 'text' => elgg_view_icon('delete-alt'), + 'title' => elgg_echo('widget:delete', array($widget->getTitle())), + 'href' => "action/widgets/delete?widget_guid=$widget->guid", + 'is_action' => true, + 'class' => 'elgg-widget-delete-button', + 'id' => "elgg-widget-delete-button-$widget->guid", + 'priority' => 900 + ); + $return[] = ElggMenuItem::factory($delete); + + if ($show_edit) { + $edit = array( + 'name' => 'settings', + 'text' => elgg_view_icon('settings-alt'), + 'title' => elgg_echo('widget:edit'), + 'href' => "#widget-edit-$widget->guid", + 'class' => "elgg-widget-edit-button", + 'rel' => 'toggle', + 'priority' => 800, + ); + $return[] = ElggMenuItem::factory($edit); + } + } + + return $return; +} + +/** + * Adds a delete link to "generic_comment" annotations + * @access private + */ +function elgg_annotation_menu_setup($hook, $type, $return, $params) { + $annotation = $params['annotation']; + /* @var ElggAnnotation $annotation */ + + if ($annotation->name == 'generic_comment' && $annotation->canEdit()) { + $url = elgg_http_add_url_query_elements('action/comments/delete', array( + 'annotation_id' => $annotation->id, + )); + + $options = array( + 'name' => 'delete', + 'href' => $url, + 'text' => "<span class=\"elgg-icon elgg-icon-delete\"></span>", + 'confirm' => elgg_echo('deleteconfirm'), + 'encode_text' => false + ); + $return[] = ElggMenuItem::factory($options); + } + + return $return; +} + + +/** + * Navigation initialization + * @access private + */ +function elgg_nav_init() { + elgg_register_plugin_hook_handler('prepare', 'menu:site', 'elgg_site_menu_setup'); + elgg_register_plugin_hook_handler('register', 'menu:river', 'elgg_river_menu_setup'); + elgg_register_plugin_hook_handler('register', 'menu:entity', 'elgg_entity_menu_setup'); + elgg_register_plugin_hook_handler('register', 'menu:widget', 'elgg_widget_menu_setup'); + elgg_register_plugin_hook_handler('register', 'menu:annotation', 'elgg_annotation_menu_setup'); +} + +elgg_register_event_handler('init', 'system', 'elgg_nav_init'); diff --git a/engine/lib/notification.php b/engine/lib/notification.php index adc4ebace..be0c359d4 100644 --- a/engine/lib/notification.php +++ b/engine/lib/notification.php @@ -3,38 +3,42 @@ * Notifications * This file contains classes and functions which allow plugins to register and send notifications. * - * There are notification methods which are provided out of the box (see notification_init() ). Each method - * is identified by a string, e.g. "email". + * There are notification methods which are provided out of the box + * (see notification_init() ). Each method is identified by a string, e.g. "email". * - * To register an event use register_notification_handler() and pass the method name and a handler function. + * To register an event use register_notification_handler() and pass the method name and a + * handler function. * - * To send a notification call notify() passing it the method you wish to use combined with a number of method - * specific addressing parameters. + * To send a notification call notify() passing it the method you wish to use combined with a + * number of method specific addressing parameters. * * Catch NotificationException to trap errors. * - * @package Elgg - * @subpackage API - - * @author Curverider Ltd - - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Notifications */ /** Notification handlers */ +global $NOTIFICATION_HANDLERS; $NOTIFICATION_HANDLERS = array(); /** * This function registers a handler for a given notification type (eg "email") * - * @param string $method The method - * @param string $handler The handler function, in the format "handler(ElggEntity $from, ElggUser $to, $subject, $message, array $params = NULL)". This function should return false on failure, and true/a tracking message ID on success. - * @param array $params A associated array of other parameters for this handler defining some properties eg. supported message length or rich text support. + * @param string $method The method + * @param string $handler The handler function, in the format + * "handler(ElggEntity $from, ElggUser $to, $subject, + * $message, array $params = NULL)". This function should + * return false on failure, and true/a tracking message ID on success. + * @param array $params An associated array of other parameters for this handler + * defining some properties eg. supported msg length or rich text support. + * + * @return bool */ function register_notification_handler($method, $handler, $params = NULL) { global $NOTIFICATION_HANDLERS; - if (is_callable($handler)) { + if (is_callable($handler, true)) { $NOTIFICATION_HANDLERS[$method] = new stdClass; $NOTIFICATION_HANDLERS[$method]->handler = $handler; @@ -51,20 +55,38 @@ function register_notification_handler($method, $handler, $params = NULL) { } /** + * This function unregisters a handler for a given notification type (eg "email") + * + * @param string $method The method + * + * @return void + * @since 1.7.1 + */ +function unregister_notification_handler($method) { + global $NOTIFICATION_HANDLERS; + + if (isset($NOTIFICATION_HANDLERS[$method])) { + unset($NOTIFICATION_HANDLERS[$method]); + } +} + +/** * Notify a user via their preferences. * - * @param mixed $to Either a guid or an array of guid's to notify. - * @param int $from GUID of the sender, which may be a user, site or object. - * @param string $subject Message subject. - * @param string $message Message body. - * @param array $params Misc additional parameters specific to various methods. - * @param mixed $methods_override A string, or an array of strings specifying the delivery methods to use - or leave blank - * for delivery using the user's chosen delivery methods. + * @param mixed $to Either a guid or an array of guid's to notify. + * @param int $from GUID of the sender, which may be a user, site or object. + * @param string $subject Message subject. + * @param string $message Message body. + * @param array $params Misc additional parameters specific to various methods. + * @param mixed $methods_override A string, or an array of strings specifying the delivery + * methods to use - or leave blank for delivery using the + * user's chosen delivery methods. + * * @return array Compound array of each delivery user/delivery method's success or failure. * @throws NotificationException */ function notify_user($to, $from, $subject, $message, array $params = NULL, $methods_override = "") { - global $NOTIFICATION_HANDLERS, $CONFIG; + global $NOTIFICATION_HANDLERS; // Sanitise if (!is_array($to)) { @@ -88,12 +110,15 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth // Are we overriding delivery? $methods = $methods_override; if (!$methods) { - $tmp = (array)get_user_notification_settings($guid); + $tmp = get_user_notification_settings($guid); $methods = array(); - foreach($tmp as $k => $v) { - // Add method if method is turned on for user! - if ($v) { - $methods[] = $k; + // $tmp may be false. don't cast + if (is_object($tmp)) { + foreach ($tmp as $k => $v) { + // Add method if method is turned on for user! + if ($v) { + $methods[] = $k; + } } } } @@ -101,19 +126,25 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth if ($methods) { // Deliver foreach ($methods as $method) { + + if (!isset($NOTIFICATION_HANDLERS[$method])) { + continue; + } + // Extract method details from list $details = $NOTIFICATION_HANDLERS[$method]; $handler = $details->handler; + /* @var callable $handler */ - if ((!$NOTIFICATION_HANDLERS[$method]) || (!$handler)) { - error_log(sprintf(elgg_echo('NotificationException:NoHandlerFound'), $method)); + if ((!$NOTIFICATION_HANDLERS[$method]) || (!$handler) || (!is_callable($handler))) { + error_log(elgg_echo('NotificationException:NoHandlerFound', array($method))); } elgg_log("Sending message to $guid using $method"); // Trigger handler and retrieve result. try { - $result[$guid][$method] = $handler( + $result[$guid][$method] = call_user_func($handler, $from ? get_entity($from) : NULL, // From entity get_entity($guid), // To entity $subject, // The subject @@ -136,16 +167,22 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth * Get the notification settings for a given user. * * @param int $user_guid The user id - * @return stdClass + * + * @return stdClass|false */ function get_user_notification_settings($user_guid = 0) { $user_guid = (int)$user_guid; if ($user_guid == 0) { - $user_guid = get_loggedin_userid(); + $user_guid = elgg_get_logged_in_user_guid(); } - $all_metadata = get_metadata_for_entity($user_guid); + // @todo: there should be a better way now that metadata is cached. E.g. just query for MD names, then + // query user object directly + $all_metadata = elgg_get_metadata(array( + 'guid' => $user_guid, + 'limit' => 0 + )); if ($all_metadata) { $prefix = "notification:method:"; $return = new stdClass; @@ -168,9 +205,10 @@ function get_user_notification_settings($user_guid = 0) { /** * Set a user notification pref. * - * @param int $user_guid The user id. - * @param string $method The delivery method (eg. email) - * @param bool $value On(true) or off(false). + * @param int $user_guid The user id. + * @param string $method The delivery method (eg. email) + * @param bool $value On(true) or off(false). + * * @return bool */ function set_user_notification_setting($user_guid, $method, $value) { @@ -179,7 +217,7 @@ function set_user_notification_setting($user_guid, $method, $value) { $user = get_entity($user_guid); if (!$user) { - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); } if (($user) && ($user instanceof ElggUser)) { @@ -194,131 +232,178 @@ function set_user_notification_setting($user_guid, $method, $value) { } /** - * Notification exception. - * @author Curverider Ltd - */ -class NotificationException extends Exception {} - - -/** * Send a notification via email. * - * @param ElggEntity $from The from user/site/object - * @param ElggUser $to To which user? - * @param string $subject The subject of the message. - * @param string $message The message body - * @param array $params Optional parameters (none taken in this instance) + * @param ElggEntity $from The from user/site/object + * @param ElggUser $to To which user? + * @param string $subject The subject of the message. + * @param string $message The message body + * @param array $params Optional parameters (none taken in this instance) + * * @return bool + * @throws NotificationException + * @access private */ -function email_notify_handler(ElggEntity $from, ElggUser $to, $subject, $message, array $params = NULL) { +function email_notify_handler(ElggEntity $from, ElggUser $to, $subject, $message, +array $params = NULL) { + global $CONFIG; if (!$from) { - throw new NotificationException(sprintf(elgg_echo('NotificationException:MissingParameter'), 'from')); + $msg = elgg_echo('NotificationException:MissingParameter', array('from')); + throw new NotificationException($msg); } if (!$to) { - throw new NotificationException(sprintf(elgg_echo('NotificationException:MissingParameter'), 'to')); + $msg = elgg_echo('NotificationException:MissingParameter', array('to')); + throw new NotificationException($msg); } - if ($to->email=="") { - throw new NotificationException(sprintf(elgg_echo('NotificationException:NoEmailAddress'), $to->guid)); + if ($to->email == "") { + $msg = elgg_echo('NotificationException:NoEmailAddress', array($to->guid)); + throw new NotificationException($msg); } - // Sanitise subject - $subject = preg_replace("/(\r\n|\r|\n)/", " ", $subject); // Strip line endings - // To $to = $to->email; // From - $site = get_entity($CONFIG->site_guid); + $site = elgg_get_site_entity(); // If there's an email address, use it - but only if its not from a user. - if ((isset($from->email)) && (!($from instanceof ElggUser))) { + if (!($from instanceof ElggUser) && $from->email) { $from = $from->email; - } else if (($site) && (isset($site->email))) { - // Has the current site got a from email address? + } else if ($site && $site->email) { + // Use email address of current site if we cannot use sender's email $from = $site->email; - } else if (isset($from->url)) { - // If we have a url then try and use that. - $breakdown = parse_url($from->url); - $from = 'noreply@' . $breakdown['host']; // Handle anything with a url } else { // If all else fails, use the domain of the site. $from = 'noreply@' . get_site_domain($CONFIG->site_guid); } - if (is_callable('mb_internal_encoding')) { - mb_internal_encoding('UTF-8'); + return elgg_send_email($from, $to, $subject, $message); +} + +/** + * Send an email to any email address + * + * @param string $from Email address or string: "name <email>" + * @param string $to Email address or string: "name <email>" + * @param string $subject The subject of the message + * @param string $body The message body + * @param array $params Optional parameters (none used in this function) + * + * @return bool + * @throws NotificationException + * @since 1.7.2 + */ +function elgg_send_email($from, $to, $subject, $body, array $params = NULL) { + global $CONFIG; + + if (!$from) { + $msg = elgg_echo('NotificationException:MissingParameter', array('from')); + throw new NotificationException($msg); } - $site = get_entity($CONFIG->site_guid); - $sitename = $site->name; - if (is_callable('mb_encode_mimeheader')) { - $sitename = mb_encode_mimeheader($site->name,"UTF-8", "B"); + + if (!$to) { + $msg = elgg_echo('NotificationException:MissingParameter', array('to')); + throw new NotificationException($msg); + } + + // return TRUE/FALSE to stop elgg_send_email() from sending + $mail_params = array( + 'to' => $to, + 'from' => $from, + 'subject' => $subject, + 'body' => $body, + 'params' => $params + ); + + $result = elgg_trigger_plugin_hook('email', 'system', $mail_params, NULL); + if ($result !== NULL) { + return $result; } $header_eol = "\r\n"; - if ( - (isset($CONFIG->broken_mta)) && - ($CONFIG->broken_mta) - ) { + if (isset($CONFIG->broken_mta) && $CONFIG->broken_mta) { // Allow non-RFC 2822 mail headers to support some broken MTAs $header_eol = "\n"; } - $from_email = "\"$sitename\" <$from>"; - if (strtolower(substr(PHP_OS, 0 , 3)) == 'win') { - // Windows is somewhat broken, so we use a different format from header - $from_email = "$from"; + // Windows is somewhat broken, so we use just address for to and from + if (strtolower(substr(PHP_OS, 0, 3)) == 'win') { + // strip name from to and from + if (strpos($to, '<')) { + preg_match('/<(.*)>/', $to, $matches); + $to = $matches[1]; + } + if (strpos($from, '<')) { + preg_match('/<(.*)>/', $from, $matches); + $from = $matches[1]; + } } - $headers = "From: $from_email{$header_eol}" + $headers = "From: $from{$header_eol}" . "Content-Type: text/plain; charset=UTF-8; format=flowed{$header_eol}" . "MIME-Version: 1.0{$header_eol}" . "Content-Transfer-Encoding: 8bit{$header_eol}"; + + // Sanitise subject by stripping line endings + $subject = preg_replace("/(\r\n|\r|\n)/", " ", $subject); + // this is because Elgg encodes everything and matches what is done with body + $subject = html_entity_decode($subject, ENT_COMPAT, 'UTF-8'); // Decode any html entities if (is_callable('mb_encode_mimeheader')) { - $subject = mb_encode_mimeheader($subject,"UTF-8", "B"); + $subject = mb_encode_mimeheader($subject, "UTF-8", "B"); } // Format message - $message = html_entity_decode($message, ENT_COMPAT, 'UTF-8'); // Decode any html entities - $message = strip_tags($message); // Strip tags from message - $message = preg_replace("/(\r\n|\r)/", "\n", $message); // Convert to unix line endings in body - $message = preg_replace("/^From/", ">From", $message); // Change lines starting with From to >From + $body = html_entity_decode($body, ENT_COMPAT, 'UTF-8'); // Decode any html entities + $body = elgg_strip_tags($body); // Strip tags from message + $body = preg_replace("/(\r\n|\r)/", "\n", $body); // Convert to unix line endings in body + $body = preg_replace("/^From/", ">From", $body); // Change lines starting with From to >From - return mail($to, $subject, wordwrap($message), $headers); + return mail($to, $subject, wordwrap($body), $headers); } /** * Correctly initialise notifications and register the email handler. * + * @return void + * @access private */ function notification_init() { // Register a notification handler for the default email method register_notification_handler("email", "email_notify_handler"); // Add settings view to user settings & register action - extend_elgg_settings_page('notifications/settings/usersettings', 'usersettings/user'); - - register_plugin_hook('usersettings:save','user','notification_user_settings_save'); + elgg_extend_view('forms/account/settings', 'core/settings/account/notifications'); - //register_action("notifications/settings/usersettings/save"); + elgg_register_plugin_hook_handler('usersettings:save', 'user', 'notification_user_settings_save'); } +/** + * Includes the action to save user notifications + * + * @return void + * @todo why can't this call action(...)? + * @access private + */ function notification_user_settings_save() { global $CONFIG; + //@todo Wha?? include($CONFIG->path . "actions/notifications/settings/usersettings/save.php"); } /** * Register an entity type and subtype to be eligible for notifications * - * @param string $entity_type The type of entity + * @param string $entity_type The type of entity * @param string $object_subtype Its subtype - * @param string $english_name It's English notification string (eg "New blog post") + * @param string $language_name Its localized notification string (eg "New blog post") + * + * @return void */ -function register_notification_object($entity_type, $object_subtype, $english_name) { +function register_notification_object($entity_type, $object_subtype, $language_name) { global $CONFIG; if ($entity_type == '') { @@ -336,15 +421,16 @@ function register_notification_object($entity_type, $object_subtype, $english_na $CONFIG->register_objects[$entity_type] = array(); } - $CONFIG->register_objects[$entity_type][$object_subtype] = $english_name; + $CONFIG->register_objects[$entity_type][$object_subtype] = $language_name; } /** * Establish a 'notify' relationship between the user and a content author * - * @param int $user_guid The GUID of the user who wants to follow a user's content + * @param int $user_guid The GUID of the user who wants to follow a user's content * @param int $author_guid The GUID of the user whose content the user wants to follow - * @return true|false Depending on success + * + * @return bool Depending on success */ function register_notification_interest($user_guid, $author_guid) { return add_entity_relationship($user_guid, 'notify', $author_guid); @@ -353,9 +439,10 @@ function register_notification_interest($user_guid, $author_guid) { /** * Remove a 'notify' relationship between the user and a content author * - * @param int $user_guid The GUID of the user who is following a user's content + * @param int $user_guid The GUID of the user who is following a user's content * @param int $author_guid The GUID of the user whose content the user wants to unfollow - * @return true|false Depending on success + * + * @return bool Depending on success */ function remove_notification_interest($user_guid, $author_guid) { return remove_entity_relationship($user_guid, 'notify', $author_guid); @@ -366,15 +453,23 @@ function remove_notification_interest($user_guid, $author_guid) { * objects and attempts to send notifications to anybody who's interested * * @see register_notification_object + * + * @param string $event create + * @param string $object_type mixed + * @param mixed $object The object created + * + * @return bool + * @access private */ function object_notifications($event, $object_type, $object) { // We only want to trigger notification events for ElggEntities if ($object instanceof ElggEntity) { + /* @var ElggEntity $object */ // Get config data global $CONFIG, $SESSION, $NOTIFICATION_HANDLERS; - $hookresult = trigger_plugin_hook('object:notifications',$object_type,array( + $hookresult = elgg_trigger_plugin_hook('object:notifications', $object_type, array( 'event' => $event, 'object_type' => $object_type, 'object' => $object, @@ -395,34 +490,37 @@ function object_notifications($event, $object_type, $object) { } if (isset($CONFIG->register_objects[$object_type][$object_subtype])) { - $descr = $CONFIG->register_objects[$object_type][$object_subtype]; - $string = $descr . ": " . $object->getURL(); + $subject = $CONFIG->register_objects[$object_type][$object_subtype]; + $string = $subject . ": " . $object->getURL(); // Get users interested in content from this person and notify them // (Person defined by container_guid so we can also subscribe to groups if we want) - foreach($NOTIFICATION_HANDLERS as $method => $foo) { + foreach ($NOTIFICATION_HANDLERS as $method => $foo) { $interested_users = elgg_get_entities_from_relationship(array( + 'site_guids' => ELGG_ENTITIES_ANY_VALUE, 'relationship' => 'notify' . $method, - 'relationship_guid' => $object->container_guid, + 'relationship_guid' => $object->container_guid, 'inverse_relationship' => TRUE, - 'types' => 'user', - 'limit' => 99999 + 'type' => 'user', + 'limit' => false )); + /* @var ElggUser[] $interested_users */ if ($interested_users && is_array($interested_users)) { - foreach($interested_users as $user) { + foreach ($interested_users as $user) { if ($user instanceof ElggUser && !$user->isBanned()) { - if (($user->guid != $SESSION['user']->guid) && has_access_to_entity($object,$user) + if (($user->guid != $SESSION['user']->guid) && has_access_to_entity($object, $user) && $object->access_id != ACCESS_PRIVATE) { - $methodstring = trigger_plugin_hook('notify:entity:message',$object->getType(),array( + $body = elgg_trigger_plugin_hook('notify:entity:message', $object->getType(), array( 'entity' => $object, 'to_entity' => $user, - 'method' => $method),$string); - if (empty($methodstring) && $methodstring !== false) { - $methodstring = $string; + 'method' => $method), $string); + if (empty($body) && $body !== false) { + $body = $string; } - if ($methodstring !== false) { - notify_user($user->guid,$object->container_guid,$descr,$methodstring,NULL,array($method)); + if ($body !== false) { + notify_user($user->guid, $object->container_guid, $subject, $body, + null, array($method)); } } } @@ -434,5 +532,5 @@ function object_notifications($event, $object_type, $object) { } // Register a startup event -register_elgg_event_handler('init','system','notification_init',0); -register_elgg_event_handler('create','object','object_notifications'); +elgg_register_event_handler('init', 'system', 'notification_init', 0); +elgg_register_event_handler('create', 'object', 'object_notifications'); diff --git a/engine/lib/objects.php b/engine/lib/objects.php index 85ad92a07..ff3cc733f 100644 --- a/engine/lib/objects.php +++ b/engine/lib/objects.php @@ -1,217 +1,19 @@ <?php - /** * Elgg objects * Functions to manage multiple or single objects in an Elgg install * * @package Elgg * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ */ /** - * ElggObject - * Representation of an "object" in the system. - * - * @package Elgg - * @subpackage Core - */ -class ElggObject extends ElggEntity { - /** - * Initialise the attributes array. - * This is vital to distinguish between metadata and base parameters. - * - * Place your base parameters here. - */ - protected function initialise_attributes() { - parent::initialise_attributes(); - - $this->attributes['type'] = "object"; - $this->attributes['title'] = ""; - $this->attributes['description'] = ""; - $this->attributes['tables_split'] = 2; - } - - /** - * Construct a new object entity, optionally from a given id value. - * - * @param mixed $guid If an int, load that GUID. - * If a db row then will attempt to load the rest of the data. - * @throws Exception if there was a problem creating the object. - */ - function __construct($guid = null) { - $this->initialise_attributes(); - - if (!empty($guid)) { - // Is $guid is a DB row - either a entity row, or a object table row. - if ($guid instanceof stdClass) { - // Load the rest - if (!$this->load($guid->guid)) { - throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid)); - } - } - - // Is $guid is an ElggObject? Use a copy constructor - else if ($guid instanceof ElggObject) { - elgg_deprecated_notice('This type of usage of the ElggObject constructor was deprecated. Please use the clone method.', 1.7); - - foreach ($guid->attributes as $key => $value) { - $this->attributes[$key] = $value; - } - } - - // Is this is an ElggEntity but not an ElggObject = ERROR! - else if ($guid instanceof ElggEntity) { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggObject')); - } - - // We assume if we have got this far, $guid is an int - else if (is_numeric($guid)) { - if (!$this->load($guid)) { - IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid)); - } - } - - else { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); - } - } - } - - /** - * Override the load function. - * This function will ensure that all data is loaded (were possible), so - * if only part of the ElggObject is loaded, it'll load the rest. - * - * @param int $guid - * @return true|false - */ - protected function load($guid) { - // Test to see if we have the generic stuff - if (!parent::load($guid)) { - return false; - } - - // Check the type - if ($this->attributes['type']!='object') { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class())); - } - - // Load missing data - $row = get_object_entity_as_row($guid); - if (($row) && (!$this->isFullyLoaded())) { - // If $row isn't a cached copy then increment the counter - $this->attributes['tables_loaded'] ++; - } - - // Now put these into the attributes array as core values - $objarray = (array) $row; - foreach($objarray as $key => $value) { - $this->attributes[$key] = $value; - } - - return true; - } - - /** - * Override the save function. - * @return true|false - */ - public function save() { - // Save generic stuff - if (!parent::save()) { - return false; - } - - // Now save specific stuff - return create_object_entity($this->get('guid'), $this->get('title'), $this->get('description'), $this->get('container_guid')); - } - - /** - * Get sites that this object is a member of - * - * @param string $subtype Optionally, the subtype of result we want to limit to - * @param int $limit The number of results to return - * @param int $offset Any indexing offset - */ - function getSites($subtype="", $limit = 10, $offset = 0) { - return get_site_objects($this->getGUID(), $subtype, $limit, $offset); - } - - /** - * Add this object to a particular site - * - * @param int $site_guid The guid of the site to add it to - * @return true|false - */ - function addToSite($site_guid) { - return add_site_object($this->getGUID(), $site_guid); - } - - /** - * Set the container for this object. - * - * @param int $container_guid The ID of the container. - * @return bool - */ - function setContainer($container_guid) { - $container_guid = (int)$container_guid; - - return $this->set('container_guid', $container_guid); - } - - /** - * Return the container GUID of this object. - * - * @return int - */ - function getContainer() { - return $this->get('container_guid'); - } - - /** - * As getContainer(), but returns the whole entity. - * - * @return mixed ElggGroup object or false. - */ - function getContainerEntity() { - $result = get_entity($this->getContainer()); - - if (($result) && ($result instanceof ElggGroup)) { - return $result; - } - - return false; - } - - /** - * Get the collections associated with a object. - * - * @param string $subtype Optionally, the subtype of result we want to limit to - * @param int $limit The number of results to return - * @param int $offset Any indexing offset - * @return unknown - */ - //public function getCollections($subtype="", $limit = 10, $offset = 0) { get_object_collections($this->getGUID(), $subtype, $limit, $offset); } - - // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// - - /** - * Return an array of fields which can be exported. - */ - public function getExportableValues() { - return array_merge(parent::getExportableValues(), array( - 'title', - 'description', - )); - } -} - -/** * Return the object specific details of a object by a row. * - * @param int $guid + * @param int $guid The guid to retreive + * + * @return bool + * @access private */ function get_object_entity_as_row($guid) { global $CONFIG; @@ -224,9 +26,12 @@ function get_object_entity_as_row($guid) { * Create or update the extras table for a given object. * Call create_entity first. * - * @param int $guid The guid of the entity you're creating (as obtained by create_entity) - * @param string $title The title of the object + * @param int $guid The guid of the entity you're creating (as obtained by create_entity) + * @param string $title The title of the object * @param string $description The object's description + * + * @return bool + * @access private */ function create_object_entity($guid, $title, $description) { global $CONFIG; @@ -239,27 +44,30 @@ function create_object_entity($guid, $title, $description) { if ($row) { // Core entities row exists and we have access to it - if ($exists = get_data_row("SELECT guid from {$CONFIG->dbprefix}objects_entity where guid = {$guid}")) { - $result = update_data("UPDATE {$CONFIG->dbprefix}objects_entity set title='$title', description='$description' where guid=$guid"); - if ($result!=false) { + $query = "SELECT guid from {$CONFIG->dbprefix}objects_entity where guid = {$guid}"; + if ($exists = get_data_row($query)) { + $query = "UPDATE {$CONFIG->dbprefix}objects_entity + set title='$title', description='$description' where guid=$guid"; + + $result = update_data($query); + if ($result != false) { // Update succeeded, continue $entity = get_entity($guid); - if (trigger_elgg_event('update',$entity->type,$entity)) { - return $guid; - } else { - $entity->delete(); - } + elgg_trigger_event('update', $entity->type, $entity); + return $guid; } } else { // Update failed, attempt an insert. - $result = insert_data("INSERT into {$CONFIG->dbprefix}objects_entity (guid, title, description) values ($guid, '$title','$description')"); - if ($result!==false) { + $query = "INSERT into {$CONFIG->dbprefix}objects_entity + (guid, title, description) values ($guid, '$title','$description')"; + + $result = insert_data($query); + if ($result !== false) { $entity = get_entity($guid); - if (trigger_elgg_event('create',$entity->type,$entity)) { + if (elgg_trigger_event('create', $entity->type, $entity)) { return $guid; } else { $entity->delete(); - //delete_entity($guid); } } } @@ -269,72 +77,12 @@ function create_object_entity($guid, $title, $description) { } /** - * THIS FUNCTION IS DEPRECATED. - * - * Delete a object's extra data. - * - * @param int $guid - */ -function delete_object_entity($guid) { - system_message(sprintf(elgg_echo('deprecatedfunction'), 'delete_user_entity')); - - return 1; // Always return that we have deleted one row in order to not break existing code. -} - -/** - * Searches for an object based on a complete or partial title or description using full text searching. - * - * IMPORTANT NOTE: With MySQL's default setup: - * 1) $criteria must be 4 or more characters long - * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! - * - * @param string $criteria The partial or full name or username. - * @param int $limit Limit of the search. - * @param int $offset Offset. - * @param string $order_by The order. - * @param boolean $count Whether to return the count of results or just the results. - * @deprecated 1.7 - */ -function search_for_object($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { - elgg_deprecated_notice('search_for_object() was deprecated by new search plugin.', 1.7); - global $CONFIG; - - $criteria = sanitise_string($criteria); - $limit = (int)$limit; - $offset = (int)$offset; - $order_by = sanitise_string($order_by); - $container_guid = (int)$container_guid; - - $access = get_access_sql_suffix("e"); - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT e.* "; - } - $query .= "from {$CONFIG->dbprefix}entities e join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid where match(o.title,o.description) against ('$criteria') and $access"; - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } - } - return false; -} - -/** * Get the sites this object is part of * * @param int $object_guid The object's GUID - * @param int $limit Number of results to return - * @param int $offset Any indexing offset + * @param int $limit Number of results to return + * @param int $offset Any indexing offset + * * @return false|array On success, an array of ElggSites */ function get_object_sites($object_guid, $limit = 10, $offset = 0) { @@ -343,16 +91,24 @@ function get_object_sites($object_guid, $limit = 10, $offset = 0) { $offset = (int)$offset; return elgg_get_entities_from_relationship(array( - 'relationship' => 'member_of_site', - 'relationship_guid' => $object_guid, - 'types' => 'site', + 'relationship' => 'member_of_site', + 'relationship_guid' => $object_guid, + 'type' => 'site', 'limit' => $limit, - 'offset' => $offset + 'offset' => $offset, )); } /** * Runs unit tests for ElggObject + * + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params + * + * @return array + * @access private */ function objects_test($hook, $type, $value, $params) { global $CONFIG; @@ -360,34 +116,5 @@ function objects_test($hook, $type, $value, $params) { return $value; } - -/** - * Returns a formatted list of objects suitable for injecting into search. - * @deprecated 1.7 - * - */ -function search_list_objects_by_name($hook, $user, $returnvalue, $tag) { - elgg_deprecated_notice('search_list_objects_by_name was deprecated by new search plugin.', 1.7); - - // Change this to set the number of users that display on the search page - $threshold = 4; - - $object = get_input('object'); - - if (!get_input('offset') && (empty($object) || $object == 'user')) { - if ($users = search_for_user($tag,$threshold)) { - $countusers = search_for_user($tag,0,0,"",true); - - $return = elgg_view('user/search/startblurb',array('count' => $countusers, 'tag' => $tag)); - foreach($users as $user) { - $return .= elgg_view_entity($user); - } - $return .= elgg_view('user/search/finishblurb',array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag)); - return $return; - - } - } -} - -register_elgg_event_handler('init', 'system', 'objects_init', 0); -register_plugin_hook('unit_test', 'system', 'objects_test'); +elgg_register_event_handler('init', 'system', 'objects_init', 0); +elgg_register_plugin_hook_handler('unit_test', 'system', 'objects_test'); diff --git a/engine/lib/opendd.php b/engine/lib/opendd.php index c582e6f77..7d635a295 100644 --- a/engine/lib/opendd.php +++ b/engine/lib/opendd.php @@ -2,316 +2,34 @@ /** * OpenDD PHP Library. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd + * @package Elgg.Core + * @subpackage ODD * @version 0.4 - * @link http://elgg.org/ */ -include_once("xml.php"); - -/** - * @class ODDDocument ODD Document container. - * This class is used during import and export to construct. - * @author Curverider Ltd - */ -class ODDDocument implements Iterator { - /** - * ODD Version - * - * @var string - */ - private $ODDSupportedVersion = "1.0"; - - /** - * Elements of the document. - */ - private $elements; - - /** - * Optional wrapper factory. - */ - private $wrapperfactory; - - public function __construct(array $elements = NULL) { - if ($elements) { - if (is_array($elements)) { - $this->elements = $elements; - } else { - $this->addElement($elements); - } - } else { - $this->elements = array(); - } - } - - /** - * Return the version of ODD being used. - * - * @return string - */ - public function getVersion() { - return $this->ODDSupportedVersion; - } - - public function getNumElements() { - return count($this->elements); - } - - public function addElement(ODD $element) { - if (!is_array($this->elements)) { - $this->elements = array(); - $this->elements[] = $element; - } - } - - public function addElements(array $elements) { - foreach ($elements as $element) { - $this->addElement($element); - } - } - - public function getElements() { - return $this->elements; - } - - /** - * Set an optional wrapper factory to optionally embed the ODD document in another format. - */ - public function setWrapperFactory(ODDWrapperFactory $factory) { - $this->wrapperfactory = $factory; - } - - /** - * Magic function to generate valid ODD XML for this item. - */ - public function __toString() { - $xml = ""; - - if ($this->wrapperfactory) { - // A wrapper has been provided - $wrapper = $this->wrapperfactory->getElementWrapper($this); // Get the wrapper for this element - - $xml = $wrapper->wrap($this); // Wrap this element (and subelements) - } else { - // Output begin tag - $generated = date("r"); - $xml .= "<odd version=\"{$this->ODDSupportedVersion}\" generated=\"$generated\">\n"; - - // Get XML for elements - foreach ($this->elements as $element) { - $xml .= "$element"; - } - - // Output end tag - $xml .= "</odd>\n"; - } - - return $xml; - } - - // 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->elements)); - } - - function current() { - return current($this->elements); - } - - function key() { - return key($this->elements); - } - - function next() { - $this->valid = (FALSE !== next($this->elements)); - } - - function valid() { - return $this->valid; - } -} - -/** - * Open Data Definition (ODD) superclass. - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - */ -abstract class ODD { - /** - * Attributes. - */ - private $attributes = array(); - - /** - * Optional body. - */ - private $body; - - /** - * Construct an ODD document with initial values. - */ - public function __construct() { - $this->body = ""; - } - - public function getAttributes() { - return $this->attributes; - } - - public function setAttribute($key, $value) { - $this->attributes[$key] = $value; - } - - public function getAttribute($key) { - if (isset($this->attributes[$key])) { - return $this->attributes[$key]; - } - - return NULL; - } - - public function setBody($value) { - $this->body = $value; - } - - public function getBody() { - return $this->body; - } - - /** - * Set the published time. - * - * @param int $time Unix timestamp - */ - public function setPublished($time) { - $this->attributes['published'] = date("r", $time); - } - - /** - * Return the published time as a unix timestamp. - * - * @return int or false on failure. - */ - public function getPublishedAsTime() { - return strtotime($this->attributes['published']); - } - - /** - * For serialisation, implement to return a string name of the tag eg "header" or "metadata". - * @return string - */ - abstract protected function getTagName(); - - /** - * Magic function to generate valid ODD XML for this item. - */ - public function __toString() { - // Construct attributes - $attr = ""; - foreach ($this->attributes as $k => $v) { - $attr .= ($v!="") ? "$k=\"$v\" " : ""; - } - - $body = $this->getBody(); - $tag = $this->getTagName(); - - $end = "/>"; - if ($body!="") { - $end = "><![CDATA[$body]]></{$tag}>"; - } - - return "<{$tag} $attr" . $end . "\n"; - } -} - -/** - * ODD Entity class. - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - */ -class ODDEntity extends ODD { - function __construct($uuid, $class, $subclass = "") { - parent::__construct(); - - $this->setAttribute('uuid', $uuid); - $this->setAttribute('class', $class); - $this->setAttribute('subclass', $subclass); - } - - protected function getTagName() { return "entity"; } -} - -/** - * ODD Metadata class. - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - */ -class ODDMetaData extends ODD { - function __construct($uuid, $entity_uuid, $name, $value, $type = "", $owner_uuid = "") { - parent::__construct(); - - $this->setAttribute('uuid', $uuid); - $this->setAttribute('entity_uuid', $entity_uuid); - $this->setAttribute('name', $name); - $this->setAttribute('type', $type); - $this->setAttribute('owner_uuid', $owner_uuid); - $this->setBody($value); - } - - protected function getTagName() { - return "metadata"; - } -} - -/** - * ODD Relationship class. - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - */ -class ODDRelationship extends ODD { - function __construct($uuid1, $type, $uuid2) { - parent::__construct(); - - $this->setAttribute('uuid1', $uuid1); - $this->setAttribute('type', $type); - $this->setAttribute('uuid2', $uuid2); - } - - protected function getTagName() { return "relationship"; } -} +// @codingStandardsIgnoreStart /** * Attempt to construct an ODD object out of a XmlElement or sub-elements. * * @param XmlElement $element The element(s) + * * @return mixed An ODD object if the element can be handled, or false. + * @access private */ -function ODD_factory(XmlElement $element) { +function ODD_factory (XmlElement $element) { $name = $element->name; $odd = false; switch ($name) { case 'entity' : - $odd = new ODDEntity("","",""); + $odd = new ODDEntity("", "", ""); break; case 'metadata' : - $odd = new ODDMetaData("","","",""); + $odd = new ODDMetaData("", "", "", ""); break; case 'relationship' : - $odd = new ODDRelationship("","",""); + $odd = new ODDRelationship("", "", ""); break; } @@ -319,15 +37,15 @@ function ODD_factory(XmlElement $element) { if ($odd) { // Attributes foreach ($element->attributes as $k => $v) { - $odd->setAttribute($k,$v); + $odd->setAttribute($k, $v); } // Body $body = $element->content; $a = stripos($body, "<![CDATA"); $b = strripos($body, "]]>"); - if (($body) && ($a!==false) && ($b!==false)) { - $body = substr($body, $a+8, $b-($a+8)); + if (($body) && ($a !== false) && ($b !== false)) { + $body = substr($body, $a + 8, $b - ($a + 8)); } $odd->setBody($body); @@ -340,7 +58,9 @@ function ODD_factory(XmlElement $element) { * Import an ODD document. * * @param string $xml The XML ODD. + * * @return ODDDocument + * @access private */ function ODD_Import($xml) { // Parse XML to an array @@ -378,8 +98,12 @@ function ODD_Import($xml) { * Export an ODD Document. * * @param ODDDocument $document The Document. - * @param ODDWrapperFactory $wrapper Optional wrapper permitting the export process to embed ODD in other document formats. + * + * @return string + * @access private */ function ODD_Export(ODDDocument $document) { return "$document"; -}
\ No newline at end of file +} + +// @codingStandardsIgnoreEnd diff --git a/engine/lib/output.php b/engine/lib/output.php new file mode 100644 index 000000000..de4f911fb --- /dev/null +++ b/engine/lib/output.php @@ -0,0 +1,469 @@ +<?php +/** + * Output functions + * Processing text for output such as pulling out URLs and extracting excerpts + * + * @package Elgg + * @subpackage Core + */ + +/** + * Takes a string and turns any URLs into formatted links + * + * @param string $text The input string + * + * @return string The output string with formatted links + */ +function parse_urls($text) { + + // URI specification: http://www.ietf.org/rfc/rfc3986.txt + // This varies from the specification in the following ways: + // * Supports non-ascii characters + // * Does not allow parentheses and single quotes + // * Cuts off commas, exclamation points, and periods off as last character + + // @todo this causes problems with <attr = "val"> + // must be in <attr="val"> format (no space). + // By default htmlawed rewrites tags to this format. + // if PHP supported conditional negative lookbehinds we could use this: + // $r = preg_replace_callback('/(?<!=)(?<![ ])?(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', + $r = preg_replace_callback('/(?<![=\/"\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\']+)/i', + create_function( + '$matches', + ' + $url = $matches[1]; + $punc = ""; + $last = substr($url, -1, 1); + if (in_array($last, array(".", "!", ",", "(", ")"))) { + $punc = $last; + $url = rtrim($url, ".!,()"); + } + $urltext = str_replace("/", "/<wbr />", $url); + return "<a href=\"$url\" rel=\"nofollow\">$urltext</a>$punc"; + ' + ), $text); + + return $r; +} + +/** + * Create paragraphs from text with line spacing + * + * @param string $pee The string + * @deprecated Use elgg_autop instead + * @todo Add deprecation warning in 1.9 + * + * @return string + **/ +function autop($pee) { + return elgg_autop($pee); +} + +/** + * Create paragraphs from text with line spacing + * + * @param string $string The string + * + * @return string + **/ +function elgg_autop($string) { + return ElggAutoP::getInstance()->process($string); +} + +/** + * Returns an excerpt. + * Will return up to n chars stopping at the nearest space. + * If no spaces are found (like in Japanese) will crop off at the + * n char mark. Adds ... if any text was chopped. + * + * @param string $text The full text to excerpt + * @param int $num_chars Return a string up to $num_chars long + * + * @return string + * @since 1.7.2 + */ +function elgg_get_excerpt($text, $num_chars = 250) { + $text = trim(elgg_strip_tags($text)); + $string_length = elgg_strlen($text); + + if ($string_length <= $num_chars) { + return $text; + } + + // handle cases + $excerpt = elgg_substr($text, 0, $num_chars); + $space = elgg_strrpos($excerpt, ' ', 0); + + // don't crop if can't find a space. + if ($space === FALSE) { + $space = $num_chars; + } + $excerpt = trim(elgg_substr($excerpt, 0, $space)); + + if ($string_length != elgg_strlen($excerpt)) { + $excerpt .= '...'; + } + + return $excerpt; +} + +/** + * Handles formatting of ampersands in urls + * + * @param string $url The URL + * + * @return string + * @since 1.7.1 + */ +function elgg_format_url($url) { + return preg_replace('/&(?!amp;)/', '&', $url); +} + +/** + * Converts an associative array into a string of well-formed attributes + * + * @note usually for HTML, but could be useful for XML too... + * + * @param array $attrs An associative array of attr => val pairs + * + * @return string HTML attributes to be inserted into a tag (e.g., <tag $attrs>) + */ +function elgg_format_attributes(array $attrs) { + $attrs = elgg_clean_vars($attrs); + $attributes = array(); + + if (isset($attrs['js'])) { + //@todo deprecated notice? + + if (!empty($attrs['js'])) { + $attributes[] = $attrs['js']; + } + + unset($attrs['js']); + } + + foreach ($attrs as $attr => $val) { + $attr = strtolower($attr); + + if ($val === TRUE) { + $val = $attr; //e.g. checked => TRUE ==> checked="checked" + } + + // ignore $vars['entity'] => ElggEntity stuff + if ($val !== NULL && $val !== false && (is_array($val) || !is_object($val))) { + + // allow $vars['class'] => array('one', 'two'); + // @todo what about $vars['style']? Needs to be semi-colon separated... + if (is_array($val)) { + $val = implode(' ', $val); + } + + $val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8', false); + $attributes[] = "$attr=\"$val\""; + } + } + + return implode(' ', $attributes); +} + +/** + * Preps an associative array for use in {@link elgg_format_attributes()}. + * + * Removes all the junk that {@link elgg_view()} puts into $vars. + * Maintains backward compatibility with attributes like 'internalname' and 'internalid' + * + * @note This function is called automatically by elgg_format_attributes(). No need to + * call it yourself before using elgg_format_attributes(). + * + * @param array $vars The raw $vars array with all it's dirtiness (config, url, etc.) + * + * @return array The array, ready to be used in elgg_format_attributes(). + * @access private + */ +function elgg_clean_vars(array $vars = array()) { + unset($vars['config']); + unset($vars['url']); + unset($vars['user']); + + // backwards compatibility code + if (isset($vars['internalname'])) { + $vars['name'] = $vars['internalname']; + unset($vars['internalname']); + } + + if (isset($vars['internalid'])) { + $vars['id'] = $vars['internalid']; + unset($vars['internalid']); + } + + if (isset($vars['__ignoreInternalid'])) { + unset($vars['__ignoreInternalid']); + } + + if (isset($vars['__ignoreInternalname'])) { + unset($vars['__ignoreInternalname']); + } + + return $vars; +} + +/** + * Converts shorthand urls to absolute urls. + * + * If the url is already absolute or protocol-relative, no change is made. + * + * @example + * elgg_normalize_url(''); // 'http://my.site.com/' + * elgg_normalize_url('dashboard'); // 'http://my.site.com/dashboard' + * elgg_normalize_url('http://google.com/'); // no change + * elgg_normalize_url('//google.com/'); // no change + * + * @param string $url The URL to normalize + * + * @return string The absolute url + */ +function elgg_normalize_url($url) { + // see https://bugs.php.net/bug.php?id=51192 + // from the bookmarks save action. + $php_5_2_13_and_below = version_compare(PHP_VERSION, '5.2.14', '<'); + $php_5_3_0_to_5_3_2 = version_compare(PHP_VERSION, '5.3.0', '>=') && + version_compare(PHP_VERSION, '5.3.3', '<'); + + if ($php_5_2_13_and_below || $php_5_3_0_to_5_3_2) { + $tmp_address = str_replace("-", "", $url); + $validated = filter_var($tmp_address, FILTER_VALIDATE_URL); + } else { + $validated = filter_var($url, FILTER_VALIDATE_URL); + } + + // work around for handling absoluate IRIs (RFC 3987) - see #4190 + if (!$validated && (strpos($url, 'http:') === 0) || (strpos($url, 'https:') === 0)) { + $validated = true; + } + + if ($validated) { + // all normal URLs including mailto: + return $url; + + } elseif (preg_match("#^(\#|\?|//)#i", $url)) { + // '//example.com' (Shortcut for protocol.) + // '?query=test', #target + return $url; + + } elseif (stripos($url, 'javascript:') === 0 || stripos($url, 'mailto:') === 0) { + // 'javascript:' and 'mailto:' + // Not covered in FILTER_VALIDATE_URL + return $url; + + } elseif (preg_match("#^[^/]*\.php(\?.*)?$#i", $url)) { + // 'install.php', 'install.php?step=step' + return elgg_get_site_url() . $url; + + } elseif (preg_match("#^[^/]*\.#i", $url)) { + // 'example.com', 'example.com/subpage' + return "http://$url"; + + } else { + // 'page/handler', 'mod/plugin/file.php' + + // trim off any leading / because the site URL is stored + // with a trailing / + return elgg_get_site_url() . ltrim($url, '/'); + } +} + +/** + * When given a title, returns a version suitable for inclusion in a URL + * + * @param string $title The title + * + * @return string The optimised title + * @since 1.7.2 + */ +function elgg_get_friendly_title($title) { + + // return a URL friendly title to short circuit normal title formatting + $params = array('title' => $title); + $result = elgg_trigger_plugin_hook('format', 'friendly:title', $params, NULL); + if ($result) { + return $result; + } + + // titles are often stored HTML encoded + $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8'); + + $title = ElggTranslit::urlize($title); + + return $title; +} + +/** + * Formats a UNIX timestamp in a friendly way (eg "less than a minute ago") + * + * @see elgg_view_friendly_time() + * + * @param int $time A UNIX epoch timestamp + * + * @return string The friendly time string + * @since 1.7.2 + */ +function elgg_get_friendly_time($time) { + + // return a time string to short circuit normal time formatting + $params = array('time' => $time); + $result = elgg_trigger_plugin_hook('format', 'friendly:time', $params, NULL); + if ($result) { + return $result; + } + + $diff = time() - (int)$time; + + $minute = 60; + $hour = $minute * 60; + $day = $hour * 24; + + if ($diff < $minute) { + return elgg_echo("friendlytime:justnow"); + } else if ($diff < $hour) { + $diff = round($diff / $minute); + if ($diff == 0) { + $diff = 1; + } + + if ($diff > 1) { + return elgg_echo("friendlytime:minutes", array($diff)); + } else { + return elgg_echo("friendlytime:minutes:singular", array($diff)); + } + } else if ($diff < $day) { + $diff = round($diff / $hour); + if ($diff == 0) { + $diff = 1; + } + + if ($diff > 1) { + return elgg_echo("friendlytime:hours", array($diff)); + } else { + return elgg_echo("friendlytime:hours:singular", array($diff)); + } + } else { + $diff = round($diff / $day); + if ($diff == 0) { + $diff = 1; + } + + if ($diff > 1) { + return elgg_echo("friendlytime:days", array($diff)); + } else { + return elgg_echo("friendlytime:days:singular", array($diff)); + } + } +} + +/** + * Strip tags and offer plugins the chance. + * Plugins register for output:strip_tags plugin hook. + * Original string included in $params['original_string'] + * + * @param string $string Formatted string + * + * @return string String run through strip_tags() and any plugin hooks. + */ +function elgg_strip_tags($string) { + $params['original_string'] = $string; + + $string = strip_tags($string); + $string = elgg_trigger_plugin_hook('format', 'strip_tags', $params, $string); + + return $string; +} + +/** + * Apply html_entity_decode() to a string while re-entitising HTML + * special char entities to prevent them from being decoded back to their + * unsafe original forms. + * + * This relies on html_entity_decode() not translating entities when + * doing so leaves behind another entity, e.g. &gt; if decoded would + * create > which is another entity itself. This seems to escape the + * usual behaviour where any two paired entities creating a HTML tag are + * usually decoded, i.e. a lone > is not decoded, but <foo> would + * be decoded to <foo> since it creates a full tag. + * + * Note: This function is poorly explained in the manual - which is really + * bad given its potential for misuse on user input already escaped elsewhere. + * Stackoverflow is littered with advice to use this function in the precise + * way that would lead to user input being capable of injecting arbitrary HTML. + * + * @param string $string + * + * @return string + * + * @author Pádraic Brady + * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com) + * @license Released under dual-license GPL2/MIT by explicit permission of Pádraic Brady + * + * @access private + */ +function _elgg_html_decode($string) { + $string = str_replace( + array('>', '<', '&', '"', '''), + array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), + $string + ); + $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8'); + $string = str_replace( + array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), + array('>', '<', '&', '"', '''), + $string + ); + return $string; +} + +/** + * Prepares query string for output to prevent CSRF attacks. + * + * @param string $string + * @return string + * + * @access private + */ +function _elgg_get_display_query($string) { + //encode <,>,&, quotes and characters above 127 + if (function_exists('mb_convert_encoding')) {
+ $display_query = mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
+ } else {
+ // if no mbstring extension, we just strip characters
+ $display_query = preg_replace("/[^\x01-\x7F]/", "", $string);
+ }
+ return htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false); +} + +/** + * Unit tests for Output + * + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params + * + * @return array + * @access private + */ +function output_unit_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/output.php'; + return $value; +} + +/** + * Initialise the Output subsystem. + * + * @return void + * @access private + */ +function output_init() { + elgg_register_plugin_hook_handler('unit_test', 'system', 'output_unit_test'); +} + +elgg_register_event_handler('init', 'system', 'output_init'); diff --git a/engine/lib/pagehandler.php b/engine/lib/pagehandler.php index 8d0f9abee..0cf99b6fe 100644 --- a/engine/lib/pagehandler.php +++ b/engine/lib/pagehandler.php @@ -2,90 +2,92 @@ /** * Elgg page handler functions * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Routing */ /** - * Turns the current page over to the page handler, allowing registered handlers to take over + * Routes the request to a registered page handler + * + * This function sets the context based on the handler name (first segment of the + * URL). It also triggers a plugin hook 'route', $handler so that plugins can + * modify the routing or handle a request. * * @param string $handler The name of the handler type (eg 'blog') - * @param array $page The parameters to the page, as an array (exploded by '/' slashes) - * @return true|false Depending on whether a registered page handler was found + * @param array $page The parameters to the page, as an array (exploded by '/' slashes) + * + * @return bool + * @access private */ function page_handler($handler, $page) { global $CONFIG; - set_context($handler); - - // if there are any query parameters, make them available from get_input - if (strpos($_SERVER['REQUEST_URI'], '?') !== FALSE) { - $query = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], '?') + 1); - if (isset($query)) { - $query_arr = elgg_parse_str($query); - if (is_array($query_arr)) { - foreach($query_arr as $name => $val) { - set_input($name, $val); - } - } - } - } - - // if page url ends in a / then last element of $page is an empty string - $page = explode('/',$page); + elgg_set_context($handler); - if (!isset($CONFIG->pagehandler) || empty($handler)) { - $result = false; - } else if (isset($CONFIG->pagehandler[$handler]) && is_callable($CONFIG->pagehandler[$handler])) { - $function = $CONFIG->pagehandler[$handler]; - $result = $function($page, $handler); - if ($result !== false) { - $result = true; - } - } else { - $result = false; + $page = explode('/', $page); + // remove empty array element when page url ends in a / (see #1480) + if ($page[count($page) - 1] === '') { + array_pop($page); } - if (!$result) { - $result = default_page_handler($page, $handler); + // return false to stop processing the request (because you handled it) + // return a new $request array if you want to route the request differently + $request = array( + 'handler' => $handler, + 'segments' => $page, + ); + $request = elgg_trigger_plugin_hook('route', $handler, null, $request); + if ($request === false) { + return true; } - if ($result !== false) { - $result = true; + + $handler = $request['handler']; + $page = $request['segments']; + + $result = false; + if (isset($CONFIG->pagehandler) + && !empty($handler) + && isset($CONFIG->pagehandler[$handler]) + && is_callable($CONFIG->pagehandler[$handler])) { + $function = $CONFIG->pagehandler[$handler]; + $result = call_user_func($function, $page, $handler); } - return $result; + return $result || headers_sent(); } /** * Registers a page handler for a particular identifier * * For example, you can register a function called 'blog_page_handler' for handler type 'blog' - * Now for all URLs of type http://yoururl/pg/blog/*, the blog_page_handler() function will be called. + * For all URLs http://yoururl/blog/*, the blog_page_handler() function will be called. * The part of the URL marked with * above will be exploded on '/' characters and passed as an * array to that function. * For example, the URL http://yoururl/blog/username/friends/ would result in the call: * blog_page_handler(array('username','friends'), blog); * - * Page handler functions should return true or the default page handler will be called. - * * A request to register a page handler with the same identifier as previously registered * handler will replace the previous one. * * The context is set to the page handler identifier before the registered * page handler function is called. For the above example, the context is set to 'blog'. * - * @param string $handler The page type to handle + * Page handlers should return true to indicate that they handled the request. + * Requests not handled are forwarded to the front page with a reason of 404. + * Plugins can register for the 'forward', '404' plugin hook. @see forward() + * + * @param string $handler The page type to handle * @param string $function Your function name - * @return true|false Depending on success + * + * @return bool Depending on success */ -function register_page_handler($handler, $function) { +function elgg_register_page_handler($handler, $function) { global $CONFIG; + if (!isset($CONFIG->pagehandler)) { $CONFIG->pagehandler = array(); } - if (is_callable($function)) { + if (is_callable($function, true)) { $CONFIG->pagehandler[$handler] = $function; return true; } @@ -94,33 +96,55 @@ function register_page_handler($handler, $function) { } /** - * A default page handler that attempts to load the actual file at a given page handler location + * Unregister a page handler for an identifier + * + * Note: to replace a page handler, call elgg_register_page_handler() * - * @param array $page The page URL elements - * @param string $handler The base handler - * @return true|false Depending on success + * @param string $handler The page type identifier + * + * @since 1.7.2 + * @return void */ -function default_page_handler($page, $handler) { +function elgg_unregister_page_handler($handler) { global $CONFIG; - $script = ""; - $page = implode('/',$page); - if (($questionmark = strripos($page, '?'))) { - $page = substr($page, 0, $questionmark); + if (!isset($CONFIG->pagehandler)) { + return; } - $script = str_replace("..","",$script); - $callpath = $CONFIG->path . $handler . "/" . $page; - if (!file_exists($callpath) || is_dir($callpath) || substr_count($callpath,'.php') == 0) { - if (substr($callpath,strlen($callpath) - 1, 1) != "/") { - $callpath .= "/"; - } - $callpath .= "index.php"; - if (!include($callpath)) { - return false; - } + + unset($CONFIG->pagehandler[$handler]); +} + +/** + * Serve an error page + * + * @todo not sending status codes yet + * + * @param string $hook The name of the hook + * @param string $type The type of the hook + * @param bool $result The current value of the hook + * @param array $params Parameters related to the hook + * @return void + */ +function elgg_error_page_handler($hook, $type, $result, $params) { + if (elgg_view_exists("errors/$type")) { + $content = elgg_view("errors/$type", $params); } else { - include($callpath); + $content = elgg_view("errors/default", $params); } + $body = elgg_view_layout('error', array('content' => $content)); + echo elgg_view_page('', $body, 'error'); + exit; +} + +/** + * Initializes the page handler/routing system + * + * @return void + * @access private + */ +function page_handler_init() { + elgg_register_plugin_hook_handler('forward', '404', 'elgg_error_page_handler'); +} - return true; -}
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'page_handler_init'); diff --git a/engine/lib/pageowner.php b/engine/lib/pageowner.php index 32c431632..bd63d08c6 100644 --- a/engine/lib/pageowner.php +++ b/engine/lib/pageowner.php @@ -1,169 +1,297 @@ <?php /** * Elgg page owner library - * Contains functions for managing page ownership + * Contains functions for managing page ownership and context * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage PageOwner */ /** - * Gets the page owner for the current page. - * @uses $CONFIG - * @return int|false The current page owner guid (0 if none). + * Gets the guid of the entity that owns the current page. + * + * @param int $guid Optional parameter used by elgg_set_page_owner_guid(). + * + * @return int The current page owner guid (0 if none). + * @since 1.8.0 */ +function elgg_get_page_owner_guid($guid = 0) { + static $page_owner_guid; -function page_owner() { - global $CONFIG; + if ($guid) { + $page_owner_guid = $guid; + } + + if (isset($page_owner_guid)) { + return $page_owner_guid; + } + + // return guid of page owner entity + $guid = elgg_trigger_plugin_hook('page_owner', 'system', NULL, 0); + + if ($guid) { + $page_owner_guid = $guid; + } + + return $guid; +} + +/** + * Gets the owner entity for the current page. + * + * @note Access is disabled when getting the page owner entity. + * + * @return ElggUser|ElggGroup|false The current page owner or false if none. + * + * @since 1.8.0 + */ +function elgg_get_page_owner_entity() { + $guid = elgg_get_page_owner_guid(); + if ($guid > 0) { + $ia = elgg_set_ignore_access(true); + $owner = get_entity($guid); + elgg_set_ignore_access($ia); + + return $owner; + } + + return false; +} + +/** + * Set the guid of the entity that owns this page + * + * @param int $guid The guid of the page owner + * @return void + * @since 1.8.0 + */ +function elgg_set_page_owner_guid($guid) { + elgg_get_page_owner_guid($guid); +} - $returnval = NULL; +/** + * Sets the page owner based on request + * + * Tries to figure out the page owner by looking at the URL or a request + * parameter. The request parameters used are 'username' and 'owner_guid'. If + * the page request is going through the page handling system, this function + * attempts to figure out the owner if the url fits the patterns of: + * <handler>/owner/<username> + * <handler>/friends/<username> + * <handler>/view/<entity guid> + * <handler>/add/<container guid> + * <handler>/edit/<entity guid> + * <handler>/group/<group guid> + * + * @note Access is disabled while finding the page owner for the group gatekeeper functions. + * + * + * @param string $hook 'page_owner' + * @param string $entity_type 'system' + * @param int $returnvalue Previous function's return value + * @param array $params no parameters + * + * @return int GUID + * @access private + */ +function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) { - $setpageowner = set_page_owner(); - if ($setpageowner !== false) { - return $setpageowner; + if ($returnvalue) { + return $returnvalue; } - if ((!isset($returnval)) && ($username = get_input("username"))) { - if (substr_count($username,'group:')) { - preg_match('/group\:([0-9]+)/i',$username,$matches); + $ia = elgg_set_ignore_access(true); + + $username = get_input("username"); + if ($username) { + // @todo using a username of group:<guid> is deprecated + if (substr_count($username, 'group:')) { + preg_match('/group\:([0-9]+)/i', $username, $matches); $guid = $matches[1]; if ($entity = get_entity($guid)) { - $returnval = $entity->getGUID(); + elgg_set_ignore_access($ia); + return $entity->getGUID(); } } - if ((!isset($returnval)) && ($user = get_user_by_username($username))) { - $returnval = $user->getGUID(); + + if ($user = get_user_by_username($username)) { + elgg_set_ignore_access($ia); + return $user->getGUID(); } } - - if ((!isset($returnval)) && ($owner = get_input("owner_guid"))) { + $owner = get_input("owner_guid"); + if ($owner) { if ($user = get_entity($owner)) { - $returnval = $user->getGUID(); + elgg_set_ignore_access($ia); + return $user->getGUID(); } } - - if ((!isset($returnval)) && (!empty($CONFIG->page_owner_handlers) && is_array($CONFIG->page_owner_handlers))) { - foreach($CONFIG->page_owner_handlers as $handler) { - if ((!isset($returnval)) && ($guid = $handler())) { - $returnval = $guid; - } - } + // ignore root and query + $uri = current_page_url(); + $path = str_replace(elgg_get_site_url(), '', $uri); + $path = trim($path, "/"); + if (strpos($path, "?")) { + $path = substr($path, 0, strpos($path, "?")); } - if (isset($returnval)) { - // Check if this is obtainable, forwarding if not. - /* - * If the owner entity has been set, but is inaccessible then we forward to the dashboard. This - * catches a bunch of WSoDs. It doesn't have much of a performance hit since 99.999% of the time the next thing - * a page does after calling this function is to retrieve the owner entity - which is of course cashed. - */ - $owner_entity = get_entity($returnval); - if (!$owner_entity) { - - // Log an error - error_log(sprintf(elgg_echo('pageownerunavailable'), $returnval)); - - // Forward - forward(); + // @todo feels hacky + if (get_input('page', FALSE)) { + $segments = explode('/', $path); + if (isset($segments[1]) && isset($segments[2])) { + switch ($segments[1]) { + case 'owner': + case 'friends': + $user = get_user_by_username($segments[2]); + if ($user) { + elgg_set_ignore_access($ia); + return $user->getGUID(); + } + break; + case 'view': + case 'edit': + $entity = get_entity($segments[2]); + if ($entity) { + elgg_set_ignore_access($ia); + return $entity->getContainerGUID(); + } + break; + case 'add': + case 'group': + $entity = get_entity($segments[2]); + if ($entity) { + elgg_set_ignore_access($ia); + return $entity->getGUID(); + } + break; + } } - - // set the page owner so if we're called again we don't have to think. - set_page_owner($returnval); - return $returnval; } - return 0; + elgg_set_ignore_access($ia); } /** - * Gets the page owner for the current page. - * @uses $CONFIG - * @return ElggUser|false The current page owner (false if none). + * Sets the page context + * + * Views can modify their output based on the local context. You may want to + * display a list of blogs on a blog page or in a small widget. The rendered + * output could be different for those two contexts ('blog' vs 'widget'). + * + * Pages that pass through the page handling system set the context to the + * first string after the root url. Example: http://example.org/elgg/bookmarks/ + * results in the initial context being set to 'bookmarks'. + * + * The context is a stack so that for a widget on a profile, the context stack + * may contain first 'profile' and then 'widget'. + * + * If no context was been set, the default context returned is 'main'. + * + * @warning The context is not available until the page_handler runs (after + * the 'init, system' event processing has completed). + * + * @param string $context The context of the page + * @return bool + * @since 1.8.0 */ -function page_owner_entity() { +function elgg_set_context($context) { global $CONFIG; - $page_owner = page_owner(); - if ($page_owner > 0) { - return get_entity($page_owner); + + $context = trim($context); + + if (empty($context)) { + return false; } - return false; + $context = strtolower($context); + + array_pop($CONFIG->context); + array_push($CONFIG->context, $context); + + return true; } /** - * Adds a page owner handler - a function that will - * return the page owner if required - * (Such functions are required to return false if they don't know) - * @uses $CONFIG - * @param string $functionname The name of the function to call - * @return mixed The guid of the owner or false + * Get the current context. + * + * Since context is a stack, this is equivalent to a peek. + * + * @return string|NULL + * @since 1.8.0 */ - -function add_page_owner_handler($functionname) { +function elgg_get_context() { global $CONFIG; - if (empty($CONFIG->page_owner_handlers)) { - $CONFIG->page_owner_handlers = array(); - } - if (is_callable($functionname)) { - $CONFIG->page_owner_handlers[] = $functionname; + + if (!$CONFIG->context) { + return null; } + + return $CONFIG->context[count($CONFIG->context) - 1]; } /** - * Allows a page to manually set a page owner + * Push a context onto the top of the stack * - * @param int $entitytoset The GUID of the page owner - * @return int|false Either the page owner we've just set, or false if unset + * @param string $context The context string to add to the context stack + * @return void + * @since 1.8.0 */ -function set_page_owner($entitytoset = -1) { - static $entity; - - if (!isset($entity)) { - $entity = false; - } +function elgg_push_context($context) { + global $CONFIG; - if ($entitytoset > -1) { - $entity = $entitytoset; - } + array_push($CONFIG->context, $context); +} - return $entity; +/** + * Removes and returns the top context string from the stack + * + * @return string|NULL + * @since 1.8.0 + */ +function elgg_pop_context() { + global $CONFIG; + return array_pop($CONFIG->context); } /** - * Sets the functional context of a page + * Check if this context exists anywhere in the stack * - * @param string $context The context of the page - * @return string|false Either the context string, or false on failure + * This is useful for situations with more than one element in the stack. For + * example, a widget has a context of 'widget'. If a widget view needs to render + * itself differently based on being on the dashboard or profile pages, it + * can check the stack. + * + * @param string $context The context string to check for + * @return bool + * @since 1.8.0 */ -function set_context($context) { +function elgg_in_context($context) { global $CONFIG; - if (!empty($context)) { - $context = trim($context); - $context = strtolower($context); - $CONFIG->context = $context; - return $context; - } else { - return false; - } + + return in_array($context, $CONFIG->context); } /** - * Returns the functional context of a page + * Initializes the page owner functions * - * @return string The context, or 'main' if no context has been provided + * @note This is on the 'boot, system' event so that the context is set up quickly. + * + * @return void + * @access private */ -function get_context() { - global $CONFIG; - if (isset($CONFIG->context) && !empty($CONFIG->context)) { - return $CONFIG->context; - } - if ($context = get_plugin_name(true)) { - return $context; +function page_owner_boot() { + + elgg_register_plugin_hook_handler('page_owner', 'system', 'default_page_owner_handler'); + + // Bootstrap the context stack by setting its first entry to the handler. + // This is the first segment of the URL and the handler is set by the rewrite rules. + // @todo this does not work for actions + $handler = get_input('handler', FALSE); + if ($handler) { + elgg_set_context($handler); } - return "main"; } + +elgg_register_event_handler('boot', 'system', 'page_owner_boot'); diff --git a/engine/lib/pam.php b/engine/lib/pam.php index 590ef9fde..1c9c3bfe1 100644 --- a/engine/lib/pam.php +++ b/engine/lib/pam.php @@ -2,35 +2,42 @@ /** * Elgg Simple PAM library * Contains functions for managing authentication. - * This is not a full implementation of PAM. It supports a single facility + * This is not a full implementation of PAM. It supports a single facility * (authentication) and allows multiple policies (user authentication is the - * default). There are two control flags possible for each module: sufficient - * or required. The entire chain for a policy is processed (or until a - * required module fails). A module fails by returning false or throwing an - * exception. The order that modules are processed is determined by the order - * they are registered. For an example of a PAM, see pam_auth_userpass() in + * default). There are two control flags possible for each module: sufficient + * or required. The entire chain for a policy is processed (or until a + * required module fails). A module fails by returning false or throwing an + * exception. The order that modules are processed is determined by the order + * they are registered. For an example of a PAM, see pam_auth_userpass() in * sessions.php. - * + * * For more information on PAMs see: * http://www.freebsd.org/doc/en/articles/pam/index.html * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @see ElggPAM + * + * @package Elgg.Core + * @subpackage Authentication.PAM */ +global $_PAM_HANDLERS; $_PAM_HANDLERS = array(); -$_PAM_HANDLERS_MSG = array(); /** * Register a PAM handler. * - * @param string $handler The handler function in the format - * pam_handler($credentials = NULL); + * A PAM handler should return true if the authentication attempt passed. For a + * failure, return false or throw an exception. Returning nothing indicates that + * the handler wants to be skipped. + * + * Note, $handler must be string callback (not an array/Closure). + * + * @param string $handler Callable global handler function in the format () + * pam_handler($credentials = NULL); * @param string $importance The importance - "sufficient" (default) or "required" - * @param string $policy - the policy type, default is "user" - * @return boolean + * @param string $policy The policy type, default is "user" + * + * @return bool */ function register_pam_handler($handler, $importance = "sufficient", $policy = "user") { global $_PAM_HANDLERS; @@ -39,8 +46,9 @@ function register_pam_handler($handler, $importance = "sufficient", $policy = "u if (!isset($_PAM_HANDLERS[$policy])) { $_PAM_HANDLERS[$policy] = array(); } - - if (is_callable($handler)) { + + // @todo remove requirement that $handle be a global function + if (is_string($handler) && is_callable($handler, true)) { $_PAM_HANDLERS[$policy][$handler] = new stdClass; $_PAM_HANDLERS[$policy][$handler]->handler = $handler; @@ -56,66 +64,13 @@ function register_pam_handler($handler, $importance = "sufficient", $policy = "u * Unregisters a PAM handler. * * @param string $handler The PAM handler function name - * @param string $policy - the policy type, default is "user" + * @param string $policy The policy type, default is "user" + * + * @return void + * @since 1.7.0 */ function unregister_pam_handler($handler, $policy = "user") { global $_PAM_HANDLERS; unset($_PAM_HANDLERS[$policy][$handler]); } - -/** - * Attempt to authenticate. - * This function will process all registered PAM handlers or stop when the first - * handler fails. A handler fails by either returning false or throwing an - * exception. The advantage of throwing an exception is that it returns a message - * through the global $_PAM_HANDLERS_MSG which can be used in communication with - * a user. The order that handlers are processed is determined by the order that - * they were registered. - * - * If $credentials are provided the PAM handler should authenticate using the - * provided credentials, if not then credentials should be prompted for or - * otherwise retrieved (eg from the HTTP header or $_SESSION). - * - * @param mixed $credentials Mixed PAM handler specific credentials (e.g. username, password) - * @param string $policy - the policy type, default is "user" - * @return bool true if authenticated, false if not. - */ -function pam_authenticate($credentials = NULL, $policy = "user") { - global $_PAM_HANDLERS, $_PAM_HANDLERS_MSG; - - $_PAM_HANDLERS_MSG = array(); - - $authenticated = false; - - foreach ($_PAM_HANDLERS[$policy] as $k => $v) { - $handler = $v->handler; - $importance = $v->importance; - - try { - // Execute the handler - if ($handler($credentials)) { - // Explicitly returned true - $_PAM_HANDLERS_MSG[$k] = "Authenticated!"; - - $authenticated = true; - } else { - $_PAM_HANDLERS_MSG[$k] = "Not Authenticated."; - - // If this is required then abort. - if ($importance == 'required') { - return false; - } - } - } catch (Exception $e) { - $_PAM_HANDLERS_MSG[$k] = "$e"; - - // If this is required then abort. - if ($importance == 'required') { - return false; - } - } - } - - return $authenticated; -}
\ No newline at end of file diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php index b12ea704a..d5d3db466 100644 --- a/engine/lib/plugins.php +++ b/engine/lib/plugins.php @@ -3,297 +3,562 @@ * Elgg plugins library * Contains functions for managing plugins * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Plugins */ +/** + * Tells ElggPlugin::start() to include the start.php file. + */ +define('ELGG_PLUGIN_INCLUDE_START', 1); -/// Cache enabled plugins per page -$ENABLED_PLUGINS_CACHE = NULL; +/** + * Tells ElggPlugin::start() to automatically register the plugin's views. + */ +define('ELGG_PLUGIN_REGISTER_VIEWS', 2); /** - * PluginException - * - * A plugin Exception, thrown when an Exception occurs relating to the plugin mechanism. Subclass for specific plugin Exceptions. + * Tells ElggPlugin::start() to automatically register the plugin's languages. + */ +define('ELGG_PLUGIN_REGISTER_LANGUAGES', 4); + +/** + * Tells ElggPlugin::start() to automatically register the plugin's classes. + */ +define('ELGG_PLUGIN_REGISTER_CLASSES', 8); + +/** + * Prefix for plugin setting names * - * @package Elgg - * @subpackage Exceptions + * @todo Can't namespace these because many plugins directly call + * private settings via $entity->$name. */ -class PluginException extends Exception {} +//define('ELGG_PLUGIN_SETTING_PREFIX', 'plugin:setting:'); /** - * @class ElggPlugin Object representing a plugin's settings for a given site. - * This class is currently a stub, allowing a plugin to saving settings in an object's metadata for each site. - * @author Curverider Ltd + * Prefix for plugin user setting names */ -class ElggPlugin extends ElggObject { - protected function initialise_attributes() { - parent::initialise_attributes(); +define('ELGG_PLUGIN_USER_SETTING_PREFIX', 'plugin:user_setting:'); - $this->attributes['subtype'] = "plugin"; - } +/** + * Internal settings prefix + * + * @todo This could be resolved by promoting ElggPlugin to a 5th type. + */ +define('ELGG_PLUGIN_INTERNAL_PREFIX', 'elgg:internal:'); - public function __construct($guid = null) { - parent::__construct($guid); + +/** + * Returns a list of plugin IDs (dir names) from a dir. + * + * @param string $dir A dir to scan for plugins. Defaults to config's plugins_path. + * + * @return array + * @since 1.8.0 + * @access private + */ +function elgg_get_plugin_ids_in_dir($dir = null) { + if (!$dir) { + $dir = elgg_get_plugins_path(); } - /** - * Override entity get and sets in order to save data to private data store. - */ - public function get($name) { - // See if its in our base attribute - if (isset($this->attributes[$name])) { - return $this->attributes[$name]; + $plugin_ids = array(); + $handle = opendir($dir); + + if ($handle) { + while ($plugin_id = readdir($handle)) { + // must be directory and not begin with a . + if (substr($plugin_id, 0, 1) !== '.' && is_dir($dir . $plugin_id)) { + $plugin_ids[] = $plugin_id; + } } + } + + sort($plugin_ids); + + return $plugin_ids; +} - // No, so see if its in the private data store. - // get_private_setting() returns false if it doesn't exist - $meta = get_private_setting($this->guid, $name); +/** + * Discovers plugins in the plugins_path setting and creates ElggPlugin + * entities for them if they don't exist. If there are plugins with entities + * but not actual files, will disable the ElggPlugin entities and mark as inactive. + * The ElggPlugin object holds config data, so don't delete. + * + * @todo Crappy name? + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_generate_plugin_entities() { + // @todo $site unused, can remove? + $site = get_config('site'); + + $dir = elgg_get_plugins_path(); + $db_prefix = elgg_get_config('dbprefix'); + + $options = array( + 'type' => 'object', + 'subtype' => 'plugin', + 'selects' => array('plugin_oe.*'), + 'joins' => array("JOIN {$db_prefix}objects_entity plugin_oe on plugin_oe.guid = e.guid"), + 'limit' => ELGG_ENTITIES_NO_VALUE + ); + + $old_ia = elgg_set_ignore_access(true); + $old_access = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $known_plugins = elgg_get_entities_from_relationship($options); + /* @var ElggPlugin[] $known_plugins */ + + if (!$known_plugins) { + $known_plugins = array(); + } - if ($meta === false) { - // Can't find it, so return null - return NULL; + // map paths to indexes + $id_map = array(); + foreach ($known_plugins as $i => $plugin) { + // if the ID is wrong, delete the plugin because we can never load it. + $id = $plugin->getID(); + if (!$id) { + $plugin->delete(); + unset($known_plugins[$i]); + continue; } + $id_map[$plugin->getID()] = $i; + } + + $physical_plugins = elgg_get_plugin_ids_in_dir($dir); - return $meta; + if (!$physical_plugins) { + return false; } - /** - * Override entity get and sets in order to save data to private data store. - */ - 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; + // check real plugins against known ones + foreach ($physical_plugins as $plugin_id) { + // is this already in the db? + if (array_key_exists($plugin_id, $id_map)) { + $index = $id_map[$plugin_id]; + $plugin = $known_plugins[$index]; + // was this plugin deleted and its entity disabled? + if (!$plugin->isEnabled()) { + $plugin->enable(); + $plugin->deactivate(); + $plugin->setPriority('last'); } - $this->attributes[$name] = $value; + // remove from the list of plugins to disable + unset($known_plugins[$index]); } else { - return set_private_setting($this->guid, $name, $value); + // add new plugins + // priority is force to last in save() if not set. + $plugin = new ElggPlugin($plugin_id); + $plugin->save(); } + } - return true; + // everything remaining in $known_plugins needs to be disabled + // because they are entities, but their dirs were removed. + // don't delete the entities because they hold settings. + foreach ($known_plugins as $plugin) { + if ($plugin->isActive()) { + $plugin->deactivate(); + } + // remove the priority. + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + remove_private_setting($plugin->guid, $name); + $plugin->disable(); } + + access_show_hidden_entities($old_access); + elgg_set_ignore_access($old_ia); + + elgg_reindex_plugin_priorities(); + + return true; } /** - * Returns a list of plugins to load, in the order that they should be loaded. - * - * @return array List of plugins + * Cache a reference to this plugin by its ID + * + * @param ElggPlugin $plugin + * + * @access private */ -function get_plugin_list() { - global $CONFIG; +function _elgg_cache_plugin_by_id(ElggPlugin $plugin) { + $map = (array) elgg_get_config('plugins_by_id_map'); + $map[$plugin->getID()] = $plugin; + elgg_set_config('plugins_by_id_map', $map); +} - if (!empty($CONFIG->pluginlistcache)) { - return $CONFIG->pluginlistcache; +/** + * Returns an ElggPlugin object with the path $path. + * + * @param string $plugin_id The id (dir name) of the plugin. NOT the guid. + * @return ElggPlugin|false + * @since 1.8.0 + */ +function elgg_get_plugin_from_id($plugin_id) { + $map = (array) elgg_get_config('plugins_by_id_map'); + if (isset($map[$plugin_id])) { + return $map[$plugin_id]; } - if ($site = get_entity($CONFIG->site_guid)) { - $pluginorder = $site->pluginorder; - if (!empty($pluginorder)) { - $plugins = unserialize($pluginorder); + $plugin_id = sanitize_string($plugin_id); + $db_prefix = get_config('dbprefix'); - $CONFIG->pluginlistcache = $plugins; - return $plugins; - } else { - $plugins = array(); + $options = array( + 'type' => 'object', + 'subtype' => 'plugin', + 'joins' => array("JOIN {$db_prefix}objects_entity oe on oe.guid = e.guid"), + 'selects' => array("oe.title", "oe.description"), + 'wheres' => array("oe.title = '$plugin_id'"), + 'limit' => 1 + ); - if ($handle = opendir($CONFIG->pluginspath)) { - while ($mod = readdir($handle)) { - if (!in_array($mod,array('.','..','.svn','CVS')) && is_dir($CONFIG->pluginspath . "/" . $mod)) { - $plugins[] = $mod; - } - } - } - - sort($plugins); + $plugins = elgg_get_entities($options); - $CONFIG->pluginlistcache = $plugins; - return $plugins; - } + if ($plugins) { + return $plugins[0]; } return false; } /** - * Regenerates the list of known plugins and saves it to the current site + * Returns if a plugin exists in the system. * - * Important: You should regenerate simplecache and the viewpath cache after executing this function - * otherwise you may experience view display artifacts. Do this with the following code: + * @warning This checks only plugins that are registered in the system! + * If the plugin cache is outdated, be sure to regenerate it with + * {@link elgg_generate_plugin_objects()} first. * - * elgg_view_regenerate_simplecache(); - * elgg_filepath_cache_reset(); + * @param string $id The plugin ID. + * @since 1.8.0 + * @return bool + */ +function elgg_plugin_exists($id) { + $plugin = elgg_get_plugin_from_id($id); + + return ($plugin) ? true : false; +} + +/** + * Returns the highest priority of the plugins * - * @param array $pluginorder Optionally, a list of existing plugins and their orders - * @return array The new list of plugins and their orders + * @return int + * @since 1.8.0 + * @access private */ -function regenerate_plugin_list($pluginorder = false) { - global $CONFIG; +function elgg_get_max_plugin_priority() { + $db_prefix = get_config('dbprefix'); + $priority = elgg_namespace_plugin_private_setting('internal', 'priority'); + $plugin_subtype = get_subtype_id('object', 'plugin'); + + $q = "SELECT MAX(CAST(ps.value AS unsigned)) as max + FROM {$db_prefix}entities e, {$db_prefix}private_settings ps + WHERE ps.name = '$priority' + AND ps.entity_guid = e.guid + AND e.type = 'object' and e.subtype = $plugin_subtype"; + + $data = get_data($q); + if ($data) { + $max = $data[0]->max; + } else { + $max = 1; + } - $CONFIG->pluginlistcache = null; + // can't have a priority of 0. + return ($max) ? $max : 1; +} - if ($site = get_entity($CONFIG->site_guid)) { - if (empty($pluginorder)) { - $pluginorder = $site->pluginorder; - $pluginorder = unserialize($pluginorder); - } else { - ksort($pluginorder); - } +/** + * Returns if a plugin is active for a current site. + * + * @param string $plugin_id The plugin ID + * @param int $site_guid The site guid + * @since 1.8.0 + * @return bool + */ +function elgg_is_active_plugin($plugin_id, $site_guid = null) { + if ($site_guid) { + $site = get_entity($site_guid); + } else { + $site = elgg_get_site_entity(); + } - if (empty($pluginorder)) { - $pluginorder = array(); - } + if (!($site instanceof ElggSite)) { + return false; + } - $max = 0; - if (sizeof($pluginorder)) { - foreach($pluginorder as $key => $plugin) { - if (is_dir($CONFIG->pluginspath . "/" . $plugin)) { - if ($key > $max) - $max = $key; - } else { - unset($pluginorder[$key]); - } - } - } - // Add new plugins to the end - if ($handle = opendir($CONFIG->pluginspath)) { - while ($mod = readdir($handle)) { - if (!in_array($mod,array('.','..','.svn','CVS')) && is_dir($CONFIG->pluginspath . "/" . $mod)) { - if (!in_array($mod, $pluginorder)) { - $max = $max + 10; - $pluginorder[$max] = $mod; - } - } - } + $plugin = elgg_get_plugin_from_id($plugin_id); + + if (!$plugin) { + return false; + } + + return $plugin->isActive($site->guid); +} + +/** + * Loads all active plugins in the order specified in the tool admin panel. + * + * @note This is called on every page load. If a plugin is active and problematic, it + * will be disabled and a visible error emitted. This does not check the deps system because + * that was too slow. + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_load_plugins() { + $plugins_path = elgg_get_plugins_path(); + $start_flags = ELGG_PLUGIN_INCLUDE_START | + ELGG_PLUGIN_REGISTER_VIEWS | + ELGG_PLUGIN_REGISTER_LANGUAGES | + ELGG_PLUGIN_REGISTER_CLASSES; + + if (!$plugins_path) { + return false; + } + + // temporary disable all plugins if there is a file called 'disabled' in the plugin dir + if (file_exists("$plugins_path/disabled")) { + if (elgg_is_admin_logged_in() && elgg_in_context('admin')) { + system_message(elgg_echo('plugins:disabled')); } + return false; + } + + if (elgg_get_config('system_cache_loaded')) { + $start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_VIEWS; + } - ksort($pluginorder); + if (elgg_get_config('i18n_loaded_from_cache')) { + $start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_LANGUAGES; + } - // Now reorder the keys .. - $key = 10; - $plugins = array(); - if (sizeof($pluginorder)) { - foreach($pluginorder as $plugin) { - $plugins[$key] = $plugin; - $key = $key + 10; + $return = true; + $plugins = elgg_get_plugins('active'); + if ($plugins) { + foreach ($plugins as $plugin) { + try { + $plugin->start($start_flags); + } catch (Exception $e) { + $plugin->deactivate(); + $msg = elgg_echo('PluginException:CannotStart', + array($plugin->getID(), $plugin->guid, $e->getMessage())); + elgg_add_admin_notice('cannot_start' . $plugin->getID(), $msg); + $return = false; + + continue; } } + } - $plugins = serialize($plugins); - - $site->pluginorder = $plugins; + return $return; +} - // Regenerate caches - elgg_view_regenerate_simplecache(); - elgg_filepath_cache_reset(); +/** + * Returns an ordered list of plugins + * + * @param string $status The status of the plugins. active, inactive, or all. + * @param mixed $site_guid Optional site guid + * @return ElggPlugin[] + * @since 1.8.0 + * @access private + */ +function elgg_get_plugins($status = 'active', $site_guid = null) { + $db_prefix = get_config('dbprefix'); + $priority = elgg_namespace_plugin_private_setting('internal', 'priority'); - return $plugins; + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; + } + // grab plugins + $options = array( + 'type' => 'object', + 'subtype' => 'plugin', + 'limit' => ELGG_ENTITIES_NO_VALUE, + 'selects' => array('plugin_oe.*'), + 'joins' => array( + "JOIN {$db_prefix}private_settings ps on ps.entity_guid = e.guid", + "JOIN {$db_prefix}objects_entity plugin_oe on plugin_oe.guid = e.guid" + ), + 'wheres' => array("ps.name = '$priority'"), + 'order_by' => "CAST(ps.value as unsigned), e.guid" + ); + + switch ($status) { + case 'active': + $options['relationship'] = 'active_plugin'; + $options['relationship_guid'] = $site_guid; + $options['inverse_relationship'] = true; + break; + + case 'inactive': + $options['wheres'][] = "NOT EXISTS ( + SELECT 1 FROM {$db_prefix}entity_relationships active_er + WHERE active_er.guid_one = e.guid + AND active_er.relationship = 'active_plugin' + AND active_er.guid_two = $site_guid)"; + break; + + case 'all': + default: + break; } - return false; -} + $old_ia = elgg_set_ignore_access(true); + $plugins = elgg_get_entities_from_relationship($options); + elgg_set_ignore_access($old_ia); + return $plugins; +} /** - * For now, loads plugins directly + * Reorder plugins to an order specified by the array. + * Plugins not included in this array will be appended to the end. + * + * @note This doesn't use the ElggPlugin->setPriority() method because + * all plugins are being changed and we don't want it to automatically + * reorder plugins. * - * @todo Add proper plugin handler that launches plugins in an admin-defined order and activates them on admin request - * @package Elgg - * @subpackage Core + * @param array $order An array of plugin ids in the order to set them + * @return bool + * @since 1.8.0 + * @access private */ -function load_plugins() { - global $CONFIG; +function elgg_set_plugin_priorities(array $order) { + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + + $plugins = elgg_get_plugins('any'); + if (!$plugins) { + return false; + } - if (!empty($CONFIG->pluginspath)) { - // See if we have cached values for things - $cached_view_paths = elgg_filepath_cache_load(); - if ($cached_view_paths) { - $CONFIG->views = unserialize($cached_view_paths); + $return = true; + + // reindex to get standard counting. no need to increment by 10. + // though we do start with 1 + $order = array_values($order); + + $missing_plugins = array(); + foreach ($plugins as $plugin) { + $plugin_id = $plugin->getID(); + + if (!in_array($plugin_id, $order)) { + $missing_plugins[] = $plugin; + continue; } - // temporary disable all plugins if there is a file called 'disabled' in the plugin dir - if (file_exists($CONFIG->pluginspath . "disabled")) { - return; + $priority = array_search($plugin_id, $order) + 1; + + if (!$plugin->set($name, $priority)) { + $return = false; + break; } + } - $plugins = get_plugin_list(); - - if (sizeof($plugins)) { - foreach($plugins as $mod) { - if (is_plugin_enabled($mod)) { - if (file_exists($CONFIG->pluginspath . $mod)) { - if (!include($CONFIG->pluginspath . $mod . "/start.php")) { - // automatically disable the bad plugin - disable_plugin($mod); - - // register error rather than rendering the site unusable with exception - register_error(sprintf(elgg_echo('PluginException:MisconfiguredPlugin'), $mod)); - - // continue loading remaining plugins - continue; - } - - if (!$cached_view_paths) { - if (is_dir($CONFIG->pluginspath . $mod . "/views")) { - if ($handle = opendir($CONFIG->pluginspath . $mod . "/views")) { - while ($viewtype = readdir($handle)) { - if (!in_array($viewtype,array('.','..','.svn','CVS')) && is_dir($CONFIG->pluginspath . $mod . "/views/" . $viewtype)) { - autoregister_views("",$CONFIG->pluginspath . $mod . "/views/" . $viewtype,$CONFIG->pluginspath . $mod . "/views/", $viewtype); - } - } - } - } - } - - if (is_dir($CONFIG->pluginspath . $mod . "/languages")) { - register_translations($CONFIG->pluginspath . $mod . "/languages/"); - } - } - } + // set the missing plugins' priorities + if ($return && $missing_plugins) { + if (!isset($priority)) { + $priority = 0; + } + foreach ($missing_plugins as $plugin) { + $priority++; + if (!$plugin->set($name, $priority)) { + $return = false; + break; } } + } - // Cache results - if (!$cached_view_paths) { - elgg_filepath_cache_save(serialize($CONFIG->views)); - } + return $return; +} + +/** + * Reindexes all plugin priorities starting at 1. + * + * @todo Can this be done in a single sql command? + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_reindex_plugin_priorities() { + return elgg_set_plugin_priorities(array()); +} + +/** + * Namespaces a string to be used as a private setting for a plugin. + * + * @param string $type The type of value: user_setting or internal. + * @param string $name The name to namespace. + * @param string $id The plugin's ID to namespace with. Required for user_setting. + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_namespace_plugin_private_setting($type, $name, $id = null) { + switch ($type) { + // commented out because it breaks $plugin->$name access to variables + //case 'setting': + // $name = ELGG_PLUGIN_SETTING_PREFIX . $name; + // break; + + case 'user_setting': + if (!$id) { + $id = elgg_get_calling_plugin_id(); + } + $name = ELGG_PLUGIN_USER_SETTING_PREFIX . "$id:$name"; + break; + + case 'internal': + $name = ELGG_PLUGIN_INTERNAL_PREFIX . $name; + break; } + + return $name; } /** - * Get the name of the most recent plugin to be called in the call stack (or the plugin that owns the current page, if any). + * Get the name of the most recent plugin to be called in the + * call stack (or the plugin that owns the current page, if any). + * + * i.e., if the last plugin was in /mod/foobar/, this would return foo_bar. * - * i.e., if the last plugin was in /mod/foobar/, get_plugin_name would return foo_bar. + * @param boolean $mainfilename If set to true, this will instead determine the + * context from the main script filename called by + * the browser. Default = false. * - * @param boolean $mainfilename If set to true, this will instead determine the context from the main script filename called by the browser. Default = false. * @return string|false Plugin name, or false if no plugin name was called + * @since 1.8.0 + * @access private + * + * @todo get rid of this */ -function get_plugin_name($mainfilename = false) { +function elgg_get_calling_plugin_id($mainfilename = false) { if (!$mainfilename) { if ($backtrace = debug_backtrace()) { - foreach($backtrace as $step) { + foreach ($backtrace as $step) { $file = $step['file']; - $file = str_replace("\\","/",$file); - $file = str_replace("//","/",$file); - if (preg_match("/mod\/([a-zA-Z0-9\-\_]*)\/start\.php$/",$file,$matches)) { + $file = str_replace("\\", "/", $file); + $file = str_replace("//", "/", $file); + if (preg_match("/mod\/([a-zA-Z0-9\-\_]*)\/start\.php$/", $file, $matches)) { return $matches[1]; } } } } else { - //if (substr_count($file,'handlers/pagehandler')) { - if (preg_match("/pg\/([a-zA-Z0-9\-\_]*)\//",$_SERVER['REQUEST_URI'],$matches)) { - return $matches[1]; + //@todo this is a hack -- plugins do not have to match their page handler names! + if ($handler = get_input('handler', FALSE)) { + return $handler; } else { $file = $_SERVER["SCRIPT_NAME"]; - $file = str_replace("\\","/",$file); - $file = str_replace("//","/",$file); - if (preg_match("/mod\/([a-zA-Z0-9\-\_]*)\//",$file,$matches)) { + $file = str_replace("\\", "/", $file); + $file = str_replace("//", "/", $file); + if (preg_match("/mod\/([a-zA-Z0-9\-\_]*)\//", $file, $matches)) { return $matches[1]; } } @@ -302,460 +567,613 @@ function get_plugin_name($mainfilename = false) { } /** - * Load and parse a plugin manifest from a plugin XML file. + * Returns an array of all provides from all active plugins. * - * Example file: + * Array in the form array( + * 'provide_type' => array( + * 'provided_name' => array( + * 'version' => '1.8', + * 'provided_by' => 'provider_plugin_id' + * ) + * ) + * ) * - * <plugin_manifest> - * <field key="author" value="Curverider Ltd" /> - * <field key="version" value="1.0" /> - * <field key="description" value="My plugin description, keep it short" /> - * <field key="website" value="http://www.elgg.org/" /> - * <field key="copyright" value="(C) Curverider 2008-2010" /> - * <field key="licence" value="GNU Public License version 2" /> - * </plugin_manifest> + * @param string $type The type of provides to return + * @param string $name A specific provided name to return. Requires $provide_type. * - * @param string $plugin Plugin name. - * @return array of values + * @return array + * @since 1.8.0 + * @access private */ -function load_plugin_manifest($plugin) { - global $CONFIG; - - $xml = xml_to_object(file_get_contents($CONFIG->pluginspath . $plugin. "/manifest.xml")); - - if ($xml) { - $elements = array(); - - foreach ($xml->children as $element) { - $key = $element->attributes['key']; - $value = $element->attributes['value']; - - $elements[$key] = $value; +function elgg_get_plugins_provides($type = null, $name = null) { + static $provides = null; + $active_plugins = elgg_get_plugins('active'); + + if (!isset($provides)) { + $provides = array(); + + foreach ($active_plugins as $plugin) { + $plugin_provides = array(); + $manifest = $plugin->getManifest(); + if ($manifest instanceof ElggPluginManifest) { + $plugin_provides = $plugin->getManifest()->getProvides(); + } + if ($plugin_provides) { + foreach ($plugin_provides as $provided) { + $provides[$provided['type']][$provided['name']] = array( + 'version' => $provided['version'], + 'provided_by' => $plugin->getID() + ); + } + } } + } - return $elements; + if ($type && $name) { + if (isset($provides[$type][$name])) { + return $provides[$type][$name]; + } else { + return false; + } + } elseif ($type) { + if (isset($provides[$type])) { + return $provides[$type]; + } else { + return false; + } } - return false; + return $provides; } /** - * This function checks a plugin manifest 'elgg_version' value against the current install - * returning TRUE if the elgg_version is <= the current install's version. - * @param $manifest_elgg_version_string The build version (eg 2009010201). - * @return bool + * Checks if a plugin is currently providing $type and $name, and optionally + * checking a version. + * + * @param string $type The type of the provide + * @param string $name The name of the provide + * @param string $version A version to check against + * @param string $comparison The comparison operator to use in version_compare() + * + * @return array An array in the form array( + * 'status' => bool Does the provide exist?, + * 'value' => string The version provided + * ) + * @since 1.8.0 + * @access private */ -function check_plugin_compatibility($manifest_elgg_version_string) { - $version = get_version(); - - if (strpos($manifest_elgg_version_string, '.') === false) { - // Using version - $req_version = (int)$manifest_elgg_version_string; +function elgg_check_plugins_provides($type, $name, $version = null, $comparison = 'ge') { + $provided = elgg_get_plugins_provides($type, $name); + if (!$provided) { + return array( + 'status' => false, + 'version' => '' + ); + } - return ($version >= $req_version); + if ($version) { + $status = version_compare($provided['version'], $version, $comparison); + } else { + $status = true; } - return false; + return array( + 'status' => $status, + 'value' => $provided['version'] + ); } /** - * Shorthand function for finding the plugin settings. + * Returns an array of parsed strings for a dependency in the + * format: array( + * 'type' => requires, conflicts, or provides. + * 'name' => The name of the requirement / conflict + * 'value' => A string representing the expected value: <1, >=3, !=enabled + * 'local_value' => The current value, ("Not installed") + * 'comment' => Free form text to help resovle the problem ("Enable / Search for plugin <link>") + * ) * - * @param string $plugin_name Optional plugin name, if not specified then it is detected from where you - * are calling from. + * @param array $dep An ElggPluginPackage dependency array + * @return array + * @since 1.8.0 + * @access private */ -function find_plugin_settings($plugin_name = "") { - $options = array('type' => 'object', 'subtype' => 'plugin', 'limit' => 9999); - $plugins = elgg_get_entities($options); - $plugin_name = sanitise_string($plugin_name); - if (!$plugin_name) { - $plugin_name = get_plugin_name(); +function elgg_get_plugin_dependency_strings($dep) { + $dep_system = elgg_extract('type', $dep); + $info = elgg_extract('dep', $dep); + $type = elgg_extract('type', $info); + + if (!$dep_system || !$info || !$type) { + return false; } - if ($plugins) { - foreach ($plugins as $plugin) { - if (strcmp($plugin->title, $plugin_name)==0) { - return $plugin; + // rewrite some of these to be more readable + switch($info['comparison']) { + case 'lt': + $comparison = '<'; + break; + case 'gt': + $comparison = '>'; + break; + case 'ge': + $comparison = '>='; + break; + case 'le': + $comparison = '<='; + break; + default; + $comparison = $info['comparison']; + break; + } + + /* + 'requires' 'plugin oauth_lib' <1.3 1.3 'downgrade' + 'requires' 'php setting bob' >3 3 'change it' + 'conflicts' 'php setting' >3 4 'change it' + 'conflicted''plugin profile' any 1.8 'disable profile' + 'provides' 'plugin oauth_lib' 1.3 -- -- + 'priority' 'before blog' -- after 'move it' + */ + $strings = array(); + $strings['type'] = elgg_echo('ElggPlugin:Dependencies:' . ucwords($dep_system)); + + switch ($type) { + case 'elgg_version': + case 'elgg_release': + // 'Elgg Version' + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:Elgg'); + $strings['expected_value'] = "$comparison {$info['version']}"; + $strings['local_value'] = $dep['value']; + $strings['comment'] = ''; + break; + + case 'php_extension': + // PHP Extension %s [version] + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:PhpExtension', array($info['name'])); + if ($info['version']) { + $strings['expected_value'] = "$comparison {$info['version']}"; + $strings['local_value'] = $dep['value']; + } else { + $strings['expected_value'] = ''; + $strings['local_value'] = ''; } + $strings['comment'] = ''; + break; + + case 'php_ini': + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:PhpIni', array($info['name'])); + $strings['expected_value'] = "$comparison {$info['value']}"; + $strings['local_value'] = $dep['value']; + $strings['comment'] = ''; + break; + + case 'plugin': + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:Plugin', array($info['name'])); + $expected = $info['version'] ? "$comparison {$info['version']}" : elgg_echo('any'); + $strings['expected_value'] = $expected; + $strings['local_value'] = $dep['value'] ? $dep['value'] : '--'; + $strings['comment'] = ''; + break; + + case 'priority': + $expected_priority = ucwords($info['priority']); + $real_priority = ucwords($dep['value']); + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:Priority'); + $strings['expected_value'] = elgg_echo("ElggPlugin:Dependencies:Priority:$expected_priority", array($info['plugin'])); + $strings['local_value'] = elgg_echo("ElggPlugin:Dependencies:Priority:$real_priority", array($info['plugin'])); + $strings['comment'] = ''; + break; + } + + if ($dep['type'] == 'suggests') { + if ($dep['status']) { + $strings['comment'] = elgg_echo('ok'); + } else { + $strings['comment'] = elgg_echo('ElggPlugin:Dependencies:Suggests:Unsatisfied'); + } + } else { + if ($dep['status']) { + $strings['comment'] = elgg_echo('ok'); + } else { + $strings['comment'] = elgg_echo('error'); } } - return false; + return $strings; } /** - * Find the plugin settings for a user. + * Returns the ElggPlugin entity of the last plugin called. * - * @param string $plugin_name Plugin name. - * @param int $user_guid The guid who's settings to retrieve. - * @return array of settings in an associative array minus prefix. + * @return mixed ElggPlugin or false + * @since 1.8.0 + * @access private */ -function find_plugin_usersettings($plugin_name = "", $user_guid = 0) { - $plugin_name = sanitise_string($plugin_name); - $user_guid = (int)$user_guid; +function elgg_get_calling_plugin_entity() { + $plugin_id = elgg_get_calling_plugin_id(); - if (!$plugin_name) { - $plugin_name = get_plugin_name(); + if ($plugin_id) { + return elgg_get_plugin_from_id($plugin_id); } - if ($user_guid == 0) { - $user_guid = get_loggedin_userid(); + return false; +} + +/** + * Returns an array of all plugin settings for a user. + * + * @param mixed $user_guid The user GUID or null for the currently logged in user. + * @param string $plugin_id The plugin ID + * @param bool $return_obj Return settings as an object? This can be used to in reusable + * views where the settings are passed as $vars['entity']. + * @return array + * @since 1.8.0 + */ +function elgg_get_all_plugin_user_settings($user_guid = null, $plugin_id = null, $return_obj = false) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - // Get metadata for user - $all_metadata = get_all_private_settings($user_guid); //get_metadata_for_entity($user_guid); - if ($all_metadata) { - $prefix = "plugin:settings:$plugin_name:"; - $return = new stdClass; + if (!$plugin instanceof ElggPlugin) { + return false; + } - foreach ($all_metadata as $key => $meta) { - $name = substr($key, strlen($prefix)); - $value = $meta; + $settings = $plugin->getAllUserSettings($user_guid); - if (strpos($key, $prefix) === 0) { - $return->$name = $value; - } + if ($settings && $return_obj) { + $return = new stdClass; + + foreach ($settings as $k => $v) { + $return->$k = $v; } return $return; + } else { + return $settings; } - - return false; } /** * Set a user specific setting for a plugin. * - * @param string $name The name - note, can't be "title". - * @param mixed $value The value. - * @param int $user_guid Optional user. - * @param string $plugin_name Optional plugin name, if not specified then it is detected from where you are calling from. + * @param string $name The name - note, can't be "title". + * @param mixed $value The value. + * @param int $user_guid Optional user. + * @param string $plugin_id Optional plugin name, if not specified then it + * is detected from where you are calling from. + * + * @return bool + * @since 1.8.0 */ -function set_plugin_usersetting($name, $value, $user_guid = 0, $plugin_name = "") { - $plugin_name = sanitise_string($plugin_name); - $user_guid = (int)$user_guid; - $name = sanitise_string($name); - - if (!$plugin_name) { - $plugin_name = get_plugin_name(); +function elgg_set_plugin_user_setting($name, $value, $user_guid = null, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - $user = get_entity($user_guid); - if (!$user) { - $user = get_loggedin_user(); + if (!$plugin) { + return false; } - if (($user) && ($user instanceof ElggUser)) { - $prefix = "plugin:settings:$plugin_name:$name"; - //$user->$prefix = $value; - //$user->save(); + return $plugin->setUserSetting($name, $value, $user_guid); +} - // Hook to validate setting - $value = trigger_plugin_hook('plugin:usersetting', 'user', array( - 'user' => $user, - 'plugin' => $plugin_name, - 'name' => $name, - 'value' => $value - ), $value); +/** + * Unsets a user-specific plugin setting + * + * @param string $name Name of the setting + * @param int $user_guid Defaults to logged in user + * @param string $plugin_id Defaults to contextual plugin name + * + * @return bool + * @since 1.8.0 + */ +function elgg_unset_plugin_user_setting($name, $user_guid = null, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); + } - return set_private_setting($user->guid, $prefix, $value); + if (!$plugin) { + return false; } - return false; + return $plugin->unsetUserSetting($name, $user_guid); } /** * Get a user specific setting for a plugin. * - * @param string $name The name. - * @param string $plugin_name Optional plugin name, if not specified then it is detected from where you are calling from. + * @param string $name The name of the setting. + * @param int $user_guid Guid of owning user + * @param string $plugin_id Optional plugin name, if not specified + * it is detected from where you are calling. + * + * @return mixed + * @since 1.8.0 */ -function get_plugin_usersetting($name, $user_guid = 0, $plugin_name = "") { - $plugin_name = sanitise_string($plugin_name); - $user_guid = (int)$user_guid; - $name = sanitise_string($name); - - if (!$plugin_name) { - $plugin_name = get_plugin_name(); - } - - $user = get_entity($user_guid); - if (!$user) { - $user = get_loggedin_user(); +function elgg_get_plugin_user_setting($name, $user_guid = null, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - if (($user) && ($user instanceof ElggUser)) { - $prefix = "plugin:settings:$plugin_name:$name"; - return get_private_setting($user->guid, $prefix); //$user->$prefix; + if (!$plugin) { + return false; } - return false; + return $plugin->getUserSetting($name, $user_guid); } /** * Set a setting for a plugin. * - * @param string $name The name - note, can't be "title". - * @param mixed $value The value. - * @param string $plugin_name Optional plugin name, if not specified then it is detected from where you are calling from. + * @param string $name The name of the setting - note, can't be "title". + * @param mixed $value The value. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @return bool + * @since 1.8.0 */ -function set_plugin_setting($name, $value, $plugin_name = "") { - if (!$plugin_name) { - $plugin_name = get_plugin_name(); +function elgg_set_plugin_setting($name, $value, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - $plugin = find_plugin_settings($plugin_name); if (!$plugin) { - $plugin = new ElggPlugin(); - } - - if ($name!='title') { - // Hook to validate setting - $value = trigger_plugin_hook('plugin:setting', 'plugin', array( - 'plugin' => $plugin_name, - 'name' => $name, - 'value' => $value - ), $value); - - $plugin->title = $plugin_name; - $plugin->access_id = ACCESS_PUBLIC; - $plugin->save(); - $plugin->$name = $value; - - return $plugin->getGUID(); + return false; } - return false; + return $plugin->setSetting($name, $value); } /** * Get setting for a plugin. * - * @param string $name The name. - * @param string $plugin_name Optional plugin name, if not specified then it is detected from where you are calling from. + * @param string $name The name of the setting. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @return mixed + * @since 1.8.0 + * @todo make $plugin_id required in future version */ -function get_plugin_setting($name, $plugin_name = "") { - $plugin = find_plugin_settings($plugin_name); +function elgg_get_plugin_setting($name, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); + } - if ($plugin) { - return $plugin->$name; + if (!$plugin) { + return false; } - return false; + return $plugin->getSetting($name); } /** - * Clear a plugin setting. + * Unsets a plugin setting. + * + * @param string $name The name of the setting. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. * - * @param string $name The name. - * @param string $plugin_name Optional plugin name, if not specified then it is detected from where you are calling from. + * @return bool + * @since 1.8.0 */ -function clear_plugin_setting($name, $plugin_name = "") { - $plugin = find_plugin_settings($plugin_name); +function elgg_unset_plugin_setting($name, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); + } - if ($plugin) { - return remove_private_setting($plugin->guid, $name); + if (!$plugin) { + return false; } - return FALSE; + return $plugin->unsetSetting($name); } /** - * Clear all plugin settings. + * Unsets all plugin settings for a plugin. * - * @param string $plugin_name Optional plugin name, if not specified then it is detected from where you are calling from. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @return bool + * @since 1.8.0 */ -function clear_all_plugin_settings($plugin_name = "") { - $plugin = find_plugin_settings($plugin_name); - - if ($plugin) { - return remove_all_private_settings($plugin->guid); +function elgg_unset_all_plugin_settings($plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - return FALSE; -} - -/** - * Return an array of installed plugins. - */ -function get_installed_plugins() { - global $CONFIG; - - $installed_plugins = array(); - - if (!empty($CONFIG->pluginspath)) { - $plugins = get_plugin_list(); - - foreach($plugins as $mod) { - $installed_plugins[$mod] = array(); - $installed_plugins[$mod]['active'] = is_plugin_enabled($mod); - $installed_plugins[$mod]['manifest'] = load_plugin_manifest($mod); - } + if (!$plugin) { + return false; } - return $installed_plugins; + return $plugin->unsetAllSettings(); } /** - * Enable a plugin for a site (default current site) + * Returns entities based upon plugin settings. + * Takes all the options for {@see elgg_get_entities_from_private_settings()} + * in addition to the ones below. * - * Important: You should regenerate simplecache and the viewpath cache after executing this function - * otherwise you may experience view display artifacts. Do this with the following code: + * @param array $options Array in the format: * - * elgg_view_regenerate_simplecache(); - * elgg_filepath_cache_reset(); + * plugin_id => NULL|STR The plugin id. Defaults to calling plugin * - * @param string $plugin The plugin name. - * @param int $site_guid The site id, if not specified then this is detected. + * plugin_user_setting_names => NULL|ARR private setting names + * + * plugin_user_setting_values => NULL|ARR metadata values + * + * plugin_user_setting_name_value_pairs => NULL|ARR ( + * name => 'name', + * value => 'value', + * 'operand' => '=', + * ) + * Currently if multiple values are sent via + * an array (value => array('value1', 'value2') + * the pair's operand will be forced to "IN". + * + * plugin_user_setting_name_value_pairs_operator => NULL|STR The operator to use for combining + * (name = value) OPERATOR (name = value); default AND + * + * @return mixed int If count, int. If not count, array. false on errors. */ -function enable_plugin($plugin, $site_guid = 0) { - global $CONFIG, $ENABLED_PLUGINS_CACHE; - - $plugin = sanitise_string($plugin); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; +function elgg_get_entities_from_plugin_user_settings(array $options = array()) { + // if they're passing it don't bother + if (!isset($options['plugin_id'])) { + $options['plugin_id'] = elgg_get_calling_plugin_id(); } - $site = get_entity($site_guid); - if (!($site instanceof ElggSite)) { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $site_guid, "ElggSite")); - } + $singulars = array('plugin_user_setting_name', 'plugin_user_setting_value', + 'plugin_user_setting_name_value_pair'); + + $options = elgg_normalise_plural_options_array($options, $singulars); - // getMetadata() doesn't return an array if only one plugin is enabled - if ($enabled = $site->enabled_plugins) { - if (!is_array($enabled)) { - $enabled = array($enabled); + // rewrite plugin_user_setting_name_* to the right PS ones. + $map = array( + 'plugin_user_setting_names' => 'private_setting_names', + 'plugin_user_setting_values' => 'private_setting_values', + 'plugin_user_setting_name_value_pairs' => 'private_setting_name_value_pairs', + 'plugin_user_setting_name_value_pairs_operator' => 'private_setting_name_value_pairs_operator' + ); + + foreach ($map as $plugin => $private) { + if (!isset($options[$plugin])) { + continue; + } + + if (isset($options[$private])) { + if (!is_array($options[$private])) { + $options[$private] = array($options[$private]); + } + + $options[$private] = array_merge($options[$private], $options[$plugin]); + } else { + $options[$private] = $options[$plugin]; } - } else { - $enabled = array(); } - $enabled[] = $plugin; - $enabled = array_unique($enabled); - $return = $site->setMetaData('enabled_plugins', $enabled); - $ENABLED_PLUGINS_CACHE = $enabled; + $plugin_id = $options['plugin_id']; + $prefix = elgg_namespace_plugin_private_setting('user_setting', '', $plugin_id); + $options['private_setting_name_prefix'] = $prefix; - return $return; + return elgg_get_entities_from_private_settings($options); } /** - * Disable a plugin for a site (default current site) + * Register object, plugin entities as ElggPlugin classes * - * Important: You should regenerate simplecache and the viewpath cache after executing this function - * otherwise you may experience view display artifacts. Do this with the following code: + * @return void + * @access private + */ +function plugin_run_once() { + add_subtype("object", "plugin", "ElggPlugin"); +} + +/** + * Runs unit tests for the entity objects. * - * elgg_view_regenerate_simplecache(); - * elgg_filepath_cache_reset(); + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params * - * @param string $plugin The plugin name. - * @param int $site_guid The site id, if not specified then this is detected. + * @return array + * @access private */ -function disable_plugin($plugin, $site_guid = 0) { - global $CONFIG, $ENABLED_PLUGINS_CACHE; - - $plugin = sanitise_string($plugin); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $site = get_entity($site_guid); - if (!($site instanceof ElggSite)) { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $site_guid, "ElggSite")); - } - - // getMetadata() doesn't return an array if only one plugin is enabled - if ($enabled = $site->enabled_plugins) { - if (!is_array($enabled)) { - $enabled = array($enabled); - } - } else { - $enabled = array(); - } - - // remove the disabled plugin from the array - if (FALSE !== $i = array_search($plugin, $enabled)) { - unset($enabled[$i]); - } - - // if we're unsetting all the plugins, this will return an empty array. - // it will fail with FALSE, though. - $return = (FALSE === $site->enabled_plugins = $enabled) ? FALSE : TRUE; - $ENABLED_PLUGINS_CACHE = $enabled; - - return $return; +function plugins_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/plugins.php'; + return $value; } /** - * Return whether a plugin is enabled or not. + * Checks on deactivate plugin event if disabling it won't create unmet dependencies and blocks disable in such case. * - * @param string $plugin The plugin name. - * @param int $site_guid The site id, if not specified then this is detected. - * @return bool + * @param string $event deactivate + * @param string $type plugin + * @param array $params Parameters array containing entry with ELggPlugin instance under 'plugin_entity' key + * @return bool false to block plugin deactivation action + * + * @access private */ -function is_plugin_enabled($plugin, $site_guid = 0) { - global $CONFIG, $ENABLED_PLUGINS_CACHE; +function _plugins_deactivate_dependency_check($event, $type, $params) { + $plugin_id = $params['plugin_entity']->getManifest()->getPluginID(); + $plugin_name = $params['plugin_entity']->getManifest()->getName(); - if (!file_exists($CONFIG->pluginspath . $plugin)) { - return false; - } + $active_plugins = elgg_get_plugins(); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - if (!$ENABLED_PLUGINS_CACHE) { - $site = get_entity($site_guid); - if (!($site instanceof ElggSite)) { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $site_guid, "ElggSite")); - } + $dependents = array(); + foreach ($active_plugins as $plugin) { + $manifest = $plugin->getManifest(); + $requires = $manifest->getRequires(); - $enabled_plugins = $site->enabled_plugins; - if ($enabled_plugins && !is_array($enabled_plugins)) { - $enabled_plugins = array($enabled_plugins); + foreach ($requires as $required) { + if ($required['type'] == 'plugin' && $required['name'] == $plugin_id) { + // there are active dependents + $dependents[$manifest->getPluginID()] = $plugin; + } } - $ENABLED_PLUGINS_CACHE = $enabled_plugins; } - foreach ($ENABLED_PLUGINS_CACHE as $e) { - if ($e == $plugin) { - return true; + if ($dependents) { + $list = '<ul>'; + // construct error message and prevent disabling + foreach ($dependents as $dependent) { + $list .= '<li>' . $dependent->getManifest()->getName() . '</li>'; } - } + $list .= '</ul>'; - return false; -} + register_error(elgg_echo('ElggPlugin:Dependencies:ActiveDependent', array($plugin_name, $list))); -/** - * Run once and only once. - */ -function plugin_run_once() { - // Register a class - add_subtype("object", "plugin", "ElggPlugin"); + return false; + } } /** - * Initialise the file modules. - * Listens to system boot and registers any appropriate file types and classes + * Initialize the plugin system + * Listens to system init and registers actions + * + * @return void + * @access private */ function plugin_init() { - // Now run this stuff, but only once run_function_once("plugin_run_once"); - // Register some actions - register_action("plugins/settings/save", false, "", true); - register_action("plugins/usersettings/save"); + elgg_register_plugin_hook_handler('unit_test', 'system', 'plugins_test'); + + // note - plugins are booted by the time this handler is registered + // deactivation due to error may have already occurred + elgg_register_event_handler('deactivate', 'plugin', '_plugins_deactivate_dependency_check'); + + elgg_register_action("plugins/settings/save", '', 'admin'); + elgg_register_action("plugins/usersettings/save"); + + elgg_register_action('admin/plugins/activate', '', 'admin'); + elgg_register_action('admin/plugins/deactivate', '', 'admin'); + elgg_register_action('admin/plugins/activate_all', '', 'admin'); + elgg_register_action('admin/plugins/deactivate_all', '', 'admin'); - register_action('admin/plugins/enable', false, "", true); // Enable - register_action('admin/plugins/disable', false, "", true); // Disable - register_action('admin/plugins/enableall', false, "", true); // Enable all - register_action('admin/plugins/disableall', false, "", true); // Disable all + elgg_register_action('admin/plugins/set_priority', '', 'admin'); - register_action('admin/plugins/reorder', false, "", true); // Reorder + elgg_register_library('elgg:markdown', elgg_get_root_path() . 'vendors/markdown/markdown.php'); } -// Register a startup event -register_elgg_event_handler('init','system','plugin_init'); +elgg_register_event_handler('init', 'system', 'plugin_init'); diff --git a/engine/lib/private_settings.php b/engine/lib/private_settings.php new file mode 100644 index 000000000..7541f7b3b --- /dev/null +++ b/engine/lib/private_settings.php @@ -0,0 +1,414 @@ +<?php +/** + * Private settings for entities + * Private settings provide metadata like storage of settings for plugins + * and users. + * + * @package Elgg.Core + * @subpackage PrivateSettings + */ + +/** + * Returns entities based upon private settings. Also accepts all + * options available to elgg_get_entities(). Supports + * the singular option shortcut. + * + * @see elgg_get_entities + * + * @param array $options Array in format: + * + * private_setting_names => NULL|ARR private setting names + * + * private_setting_values => NULL|ARR metadata values + * + * private_setting_name_value_pairs => NULL|ARR ( + * name => 'name', + * value => 'value', + * 'operand' => '=', + * ) + * Currently if multiple values are sent via + * an array (value => array('value1', 'value2') + * the pair's operand will be forced to "IN". + * + * private_setting_name_value_pairs_operator => NULL|STR The operator to use for combining + * (name = value) OPERATOR (name = value); default AND + * + * private_setting_name_prefix => STR A prefix to apply to all private settings. Used to + * namespace plugin user settings or by plugins to namespace + * their own settings. + * + * + * @return mixed int If count, int. If not count, array. false on errors. + * @since 1.8.0 + */ +function elgg_get_entities_from_private_settings(array $options = array()) { + $defaults = array( + 'private_setting_names' => ELGG_ENTITIES_ANY_VALUE, + 'private_setting_values' => ELGG_ENTITIES_ANY_VALUE, + 'private_setting_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, + 'private_setting_name_value_pairs_operator' => 'AND', + 'private_setting_name_prefix' => '', + ); + + $options = array_merge($defaults, $options); + + $singulars = array('private_setting_name', 'private_setting_value', + 'private_setting_name_value_pair'); + + $options = elgg_normalise_plural_options_array($options, $singulars); + + $clauses = elgg_get_entity_private_settings_where_sql('e', $options['private_setting_names'], + $options['private_setting_values'], $options['private_setting_name_value_pairs'], + $options['private_setting_name_value_pairs_operator'], $options['private_setting_name_prefix']); + + if ($clauses) { + // merge wheres to pass to get_entities() + if (isset($options['wheres']) && !is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } elseif (!isset($options['wheres'])) { + $options['wheres'] = array(); + } + + $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']); + + // merge joins to pass to get_entities() + if (isset($options['joins']) && !is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } elseif (!isset($options['joins'])) { + $options['joins'] = array(); + } + + $options['joins'] = array_merge($options['joins'], $clauses['joins']); + } + + return elgg_get_entities($options); +} + +/** + * Returns private setting name and value SQL where/join clauses for entities. + * + * @param string $table Entities table name + * @param array|null $names Array of names + * @param array|null $values Array of values + * @param array|null $pairs Array of names / values / operands + * @param string $pair_operator Operator for joining pairs where clauses + * @param string $name_prefix A string to prefix all names with + * @return array + * @since 1.8.0 + * @access private + */ +function elgg_get_entity_private_settings_where_sql($table, $names = NULL, $values = NULL, +$pairs = NULL, $pair_operator = 'AND', $name_prefix = '') { + + global $CONFIG; + + // @todo short circuit test + + $return = array ( + 'joins' => array (), + 'wheres' => array(), + ); + + $return['joins'][] = "JOIN {$CONFIG->dbprefix}private_settings ps on + {$table}.guid = ps.entity_guid"; + + $wheres = array(); + + // get names wheres + $names_where = ''; + if ($names !== NULL) { + if (!is_array($names)) { + $names = array($names); + } + + $sanitised_names = array(); + foreach ($names as $name) { + $name = $name_prefix . $name; + $sanitised_names[] = '\'' . sanitise_string($name) . '\''; + } + + $names_str = implode(',', $sanitised_names); + if ($names_str) { + $names_where = "(ps.name IN ($names_str))"; + } + } + + // get values wheres + $values_where = ''; + if ($values !== NULL) { + if (!is_array($values)) { + $values = array($values); + } + + $sanitised_values = array(); + foreach ($values as $value) { + // normalize to 0 + if (!$value) { + $value = 0; + } + $sanitised_values[] = '\'' . sanitise_string($value) . '\''; + } + + $values_str = implode(',', $sanitised_values); + if ($values_str) { + $values_where = "(ps.value IN ($values_str))"; + } + } + + if ($names_where && $values_where) { + $wheres[] = "($names_where AND $values_where)"; + } elseif ($names_where) { + $wheres[] = "($names_where)"; + } elseif ($values_where) { + $wheres[] = "($values_where)"; + } + + // add pairs which must be in arrays. + if (is_array($pairs)) { + // join counter for incremental joins in pairs + $i = 1; + + // check if this is an array of pairs or just a single pair. + if (isset($pairs['name']) || isset($pairs['value'])) { + $pairs = array($pairs); + } + + $pair_wheres = array(); + + foreach ($pairs as $index => $pair) { + // @todo move this elsewhere? + // support shortcut 'n' => 'v' method. + if (!is_array($pair)) { + $pair = array( + 'name' => $index, + 'value' => $pair + ); + } + + // must have at least a name and value + if (!isset($pair['name']) || !isset($pair['value'])) { + // @todo should probably return false. + continue; + } + + if (isset($pair['operand'])) { + $operand = sanitise_string($pair['operand']); + } else { + $operand = ' = '; + } + + // for comparing + $trimmed_operand = trim(strtolower($operand)); + + // if the value is an int, don't quote it because str '15' < str '5' + // if the operand is IN don't quote it because quoting should be done already. + if (is_numeric($pair['value'])) { + $value = sanitise_string($pair['value']); + } else if (is_array($pair['value'])) { + $values_array = array(); + + foreach ($pair['value'] as $pair_value) { + if (is_numeric($pair_value)) { + $values_array[] = sanitise_string($pair_value); + } else { + $values_array[] = "'" . sanitise_string($pair_value) . "'"; + } + } + + if ($values_array) { + $value = '(' . implode(', ', $values_array) . ')'; + } + + // @todo allow support for non IN operands with array of values. + // will have to do more silly joins. + $operand = 'IN'; + } else if ($trimmed_operand == 'in') { + $value = "({$pair['value']})"; + } else { + $value = "'" . sanitise_string($pair['value']) . "'"; + } + + $name = sanitise_string($name_prefix . $pair['name']); + + // @todo The multiple joins are only needed when the operator is AND + $return['joins'][] = "JOIN {$CONFIG->dbprefix}private_settings ps{$i} + on {$table}.guid = ps{$i}.entity_guid"; + + $pair_wheres[] = "(ps{$i}.name = '$name' AND ps{$i}.value + $operand $value)"; + + $i++; + } + + $where = implode(" $pair_operator ", $pair_wheres); + if ($where) { + $wheres[] = "($where)"; + } + } + + $where = implode(' AND ', $wheres); + if ($where) { + $return['wheres'][] = "($where)"; + } + + return $return; +} + +/** + * Gets a private setting for an entity. + * + * Plugin authors can set private data on entities. By default + * private data will not be searched or exported. + * + * @internal Private data is used to store settings for plugins + * and user settings. + * + * @param int $entity_guid The entity GUID + * @param string $name The name of the setting + * + * @return mixed The setting value, or false on failure + * @see set_private_setting() + * @see get_all_private_settings() + * @see remove_private_setting() + * @see remove_all_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function get_private_setting($entity_guid, $name) { + global $CONFIG; + $entity_guid = (int) $entity_guid; + $name = sanitise_string($name); + + $entity = get_entity($entity_guid); + if (!$entity instanceof ElggEntity) { + return false; + } + + $query = "SELECT value from {$CONFIG->dbprefix}private_settings + where name = '{$name}' and entity_guid = {$entity_guid}"; + $setting = get_data_row($query); + + if ($setting) { + return $setting->value; + } + return false; +} + +/** + * Return an array of all private settings. + * + * @param int $entity_guid The entity GUID + * + * @return array|false + * @see set_private_setting() + * @see get_private_settings() + * @see remove_private_setting() + * @see remove_all_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function get_all_private_settings($entity_guid) { + global $CONFIG; + + $entity_guid = (int) $entity_guid; + $entity = get_entity($entity_guid); + if (!$entity instanceof ElggEntity) { + return false; + } + + $query = "SELECT * from {$CONFIG->dbprefix}private_settings where entity_guid = {$entity_guid}"; + $result = get_data($query); + 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 int $entity_guid The entity GUID + * @param string $name The name of the setting + * @param string $value The value of the setting + * + * @return bool + * @see get_private_setting() + * @see get_all_private_settings() + * @see remove_private_setting() + * @see remove_all_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function set_private_setting($entity_guid, $name, $value) { + global $CONFIG; + + $entity_guid = (int) $entity_guid; + $name = sanitise_string($name); + $value = sanitise_string($value); + + $result = insert_data("INSERT into {$CONFIG->dbprefix}private_settings + (entity_guid, name, value) VALUES + ($entity_guid, '$name', '$value') + ON DUPLICATE KEY UPDATE value='$value'"); + + return $result !== false; +} + +/** + * Deletes a private setting for an entity. + * + * @param int $entity_guid The Entity GUID + * @param string $name The name of the setting + * + * @return bool + * @see get_private_setting() + * @see get_all_private_settings() + * @see set_private_setting() + * @see remove_all_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function remove_private_setting($entity_guid, $name) { + global $CONFIG; + + $entity_guid = (int) $entity_guid; + + $entity = get_entity($entity_guid); + if (!$entity instanceof ElggEntity) { + return false; + } + + $name = sanitise_string($name); + + return delete_data("DELETE from {$CONFIG->dbprefix}private_settings + WHERE name = '{$name}' + AND entity_guid = {$entity_guid}"); +} + +/** + * Deletes all private settings for an entity. + * + * @param int $entity_guid The Entity GUID + * + * @return bool + * @see get_private_setting() + * @see get_all_private_settings() + * @see set_private_setting() + * @see remove_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function remove_all_private_settings($entity_guid) { + global $CONFIG; + + $entity_guid = (int) $entity_guid; + + $entity = get_entity($entity_guid); + if (!$entity instanceof ElggEntity) { + return false; + } + + return delete_data("DELETE from {$CONFIG->dbprefix}private_settings + WHERE entity_guid = {$entity_guid}"); +} diff --git a/engine/lib/query.php b/engine/lib/query.php deleted file mode 100644 index 619a2d288..000000000 --- a/engine/lib/query.php +++ /dev/null @@ -1,889 +0,0 @@ -<?php - /** - * Elgg database query - * Contains a wrapper for performing database queries in a structured way. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - - - /** - * @class QueryComponent Query component superclass. - * Component of a query. - * @author Curverider Ltd - * @see Query - */ - abstract class QueryComponent - { - /** - * Associative array of fields and values - */ - private $fields; - - function __construct() - { - $this->fields = array(); - } - - /** - * Class member get overloading - * - * @param string $name - * @return mixed - */ - function __get($name) { - return $this->fields[$name]; - } - - /** - * Class member set overloading - * - * @param string $name - * @param mixed $value - * @return void - */ - function __set($name, $value) { - $this->fields[$name] = $value; - - return true; - } - } - - /** - * @class SelectFieldQueryComponent Class representing a select field. - * This class represents a select field component. - * @author Curverider Ltd - * @see Query - */ - class SelectFieldQueryComponent extends QueryComponent - { - /** - * Construct a select field component - * - * @param string $table The table containing the field. - * @param string $field The field or "*" - */ - function __construct($table, $field) - { - global $CONFIG; - - $this->table = $CONFIG->dbprefix . sanitise_string($table); - $this->field = sanitise_string($field); - } - - function __toString() - { - return "{$this->table}.{$this->field}"; - } - } - - /** - * @class LimitOffsetQueryComponent - * Limit and offset clauses of a query. - * @author Curverider Ltd - * @see Query - */ - class LimitOffsetQueryComponent extends QueryComponent - { - /** - * Specify a limit and an offset. - * - * @param int $limit The limit. - * @param int $offset The offset. - */ - function __construct($limit = 25, $offset = 0) - { - $this->limit = (int)$limit; - $this->offset = (int)$offset; - } - - function __toString() - { - return "limit {$this->offset}, {$this->limit}"; - } - } - - /** - * @class OrderQueryComponent - * Order the query results. - * @author Curverider Ltd - * @see Query - */ - class OrderQueryComponent extends QueryComponent - { - function __construct($table, $field, $order = "asc") - { - global $CONFIG; - - $this->table = $CONFIG->dbprefix . sanitise_string($table); - $this->field = sanitise_string($field); - $this->order = sanitise_string($order); - } - - function __toString() - { - return "order by {$this->table}.{$this->field} {$this->order}"; - } - } - - /** - * @class TableQueryComponent - * List of tables to select from or insert into. - * @author Curverider Ltd - * @see Query - */ - class TableQueryComponent extends QueryComponent - { - function __construct($table) - { - global $CONFIG; - - $this->table = $CONFIG->dbprefix . sanitise_string($table); - } - - function __toString() - { - return $this->table; - } - } - - /** - * @class AccessControlQueryComponent - * Access control component. - * @author Curverider Ltd - * @see Query - */ - class AccessControlQueryComponent extends QueryComponent - { - /** - * Construct the ACL. - * - * @param string $acl_table The table where the access control field is. - * @param string $acl_field The field containing the access control. - * @param string $object_owner_table The table containing the owner information for the stuff you're retrieving. - * @param string $object_owner_id_field The field in $object_owner_table containing the owner information - */ - function __construct($acl_table = "entities", $acl_field = "access_id", $object_owner_table = "entities", $object_owner_id_field = "owner_guid") - { - global $CONFIG; - - $this->acl_table = $CONFIG->dbprefix . sanitise_string($acl_table); - $this->acl_field = sanitise_string($acl_field); - $this->object_owner_table = $CONFIG->dbprefix . sanitise_string($object_owner_table); - $this->object_owner_id_field = sanitise_string($object_owner_id_field); - } - - function __toString() - { - //$access = get_access_list(); - // KJ - changed to use get_access_sql_suffix - // Note: currently get_access_sql_suffix is hardwired to use - // $acl_field = "access_id", $object_owner_table = $acl_table, and - // $object_owner_id_field = "owner_guid" - // TODO: recode get_access_sql_suffix to make it possible to specify alternate field names - return "and ".get_access_sql_suffix($this->acl_table); // Add access controls - - //return "and ({$this->acl_table}.{$this->acl_field} in {$access} or ({$this->acl_table}.{$this->acl_field} = 0 and {$this->object_owner_table}.{$this->object_owner_id_field} = {$_SESSION['id']}))"; - } - } - - /** - * @class JoinQueryComponent Join query. - * Represents a join query. - * @author Curverider Ltd - * @see Query - */ - class JoinQueryComponent extends QueryComponent - { - /** - * Construct a join query. - * @param string $table Table one to join... - * @param string $field Field 1 with... - * @param string $table2 Table 2 ... - * @param string $field2 Field... - * @param string $operator Using this operator - */ - function __construct($table1, $field1, $table2, $field2, $operator = "=") - { - global $CONFIG; - - $this->table1 = $CONFIG->dbprefix . sanitise_string($table1); - $this->field1 = sanitise_string($field1); - $this->table2 = $CONFIG->dbprefix . sanitise_string($table2); - $this->field2 = sanitise_string($field2); - $this->operator = sanitise_string($operator); - } - - function __toString() - { - return "join {$this->table2} on {$this->$table}.{$this->$field} {$this->$operator} {$this->$table2}.{$this->$field2}"; - } - } - - /** - * @class SetQueryComponent Set query. - * Represents an update set query. - * @author Curverider Ltd - * @see Query - */ - class SetQueryComponent extends QueryComponent - { - /** - * Construct a setting query - * - * @param string $table The table to modify - * @param string $field The field to modify - * @param mixed $value The value to set it to - */ - function __construct($table, $field, $value) - { - global $CONFIG; - - $this->table = $CONFIG->dbprefix . sanitise_string($table); - $this->field = sanitise_string($field); - if (is_numeric($value)) - $this->value = (int)$value; - else - $this->value = "'".sanitise_string($value)."'"; - } - - function __toString() - { - return "{$this->table}.{$this->field}={$this->value}"; - } - } - - /** - * @class WhereQueryComponent - * A component of a where query. - * @author Curverider Ltd - * @see Query - */ - class WhereQueryComponent extends QueryComponent - { - /** - * A where query. - * - * @param string $left_table The table on the left of the operator - * @param string $left_field The left field - * @param string $operator The operator eg "=" or "<" - * @param string $right_table The table on the right of the operator - * @param string $right_field The right field - * @param string $link_operator How this where clause links with the previous clause, eg. "and" "or" - */ - function __construct($left_table, $left_field, $operator, $right_table, $right_field, $link_operator = "and") - { - global $CONFIG; - - $this->link_operator = sanitise_string($link_operator); - $this->left_table = $CONFIG->dbprefix . sanitise_string($left_table); - $this->left_field = sanitise_string($left_field); - $this->operator = sanitise_string($operator); - $this->right_table = $CONFIG->dbprefix . sanitise_string($right_table); - $this->right_field = sanitise_string($right_field); - } - - /** - * Return the SQL without the link operator. - */ - public function toStringNoLink() - { - return "{$this->left_table }.{$this->left_field} {$this->operator} {$this->right_table}.{$this->right_field}"; - } - - function __toString() - { - return "{$this->link_operator} " . $this->toStringNoLink(); - } - } - - /** - * @class WhereStaticQueryComponent - * A component of a where query where there is no right hand table, rather a static value. - * @author Curverider Ltd - * @see Query - */ - class WhereStaticQueryComponent extends WhereQueryComponent - { - /** - * A where query. - * - * @param string $left_table The table on the left of the operator - * @param string $left_field The left field - * @param string $operator The operator eg "=" or "<" - * @param string $value The value - * @param string $link_operator How this where clause links with the previous clause, eg. "and" "or" - */ - function __construct($left_table, $left_field, $operator, $value, $link_operator = "and") - { - global $CONFIG; - - $this->link_operator = sanitise_string($link_operator); - $this->left_table = $CONFIG->dbprefix . sanitise_string($left_table); - $this->left_field = sanitise_string($left_field); - $this->operator = sanitise_string($operator); - if (is_numeric($value)) - $this->value = (int)$value; - else - $this->value = "'".sanitise_string($value)."'"; - } - - /** - * Return the SQL without the link operator. - */ - public function toStringNoLink() - { - return "{$this->left_table }.{$this->left_field} {$this->operator} {$this->value}"; - } - } - - /** - * @class WhereSetQueryComponent - * A where query that may contain other where queries (in brackets). - * @author Curverider Ltd - * @see Query - */ - class WhereSetQueryComponent extends WhereQueryComponent - { - /** - * Construct a subset of wheres. - * - * @param array $wheres An array of WhereQueryComponent - * @param string $link_operator How this where clause links with the previous clause, eg. "and" "or" - */ - function __construct(array $wheres, $link_operator = "and") - { - $this->link_operator = sanitise_string($link_operator); - $this->wheres = $wheres; - } - - public function toStringNoLink() - { - $cnt = 0; - $string = " ("; - foreach ($this->wheres as $where) { - if (!($where instanceof WhereQueryComponent)) - throw new DatabaseException(elgg_echo('DatabaseException:WhereSetNonQuery')); - - if (!$cnt) - $string.= $where->toStringNoLink(); - else - $string.=" $where "; - - $cnt ++; - } - $string .= ")"; - - return $string; - } - } - - /** - * @class QueryTypeQueryComponent - * What type of query is this? - * @author Curverider Ltd - * @see Query - */ - abstract class QueryTypeQueryComponent extends QueryComponent - { - function __toString() - { - return $this->query_type; - } - } - - /** - * @class SelectQueryTypeQueryComponent - * A select query. - * @author Curverider Ltd - * @see Query - */ - class SelectQueryTypeQueryComponent extends QueryTypeQueryComponent - { - function __construct() - { - $this->query_type = "SELECT"; - } - } - - /** - * @class InsertQueryTypeQueryComponent - * An insert query. - * @author Curverider Ltd - * @see Query - */ - class InsertQueryTypeQueryComponent extends QueryTypeQueryComponent - { - function __construct() - { - $this->query_type = "INSERT INTO"; - } - } - - /** - * @class DeleteQueryTypeQueryComponent - * A delete query. - * @author Curverider Ltd - * @see Query - */ - class DeleteQueryTypeQueryComponent extends QueryTypeQueryComponent - { - function __construct() - { - $this->query_type = "DELETE FROM"; - } - } - - /** - * @class UpdateQueryTypeQueryComponent - * An update query. - * @author Curverider Ltd - * @see Query - */ - class UpdateQueryTypeQueryComponent extends QueryTypeQueryComponent - { - function __construct() - { - $this->query_type = "UPDATE"; - } - } - - /** - * @class Query Provides a framework to construct complex queries in a safer environment. - * - * The usage of this class depends on the type of query you are executing, but the basic idea is to - * construct a query out of pluggable classes. - * - * Once constructed SQL can be generated using the toString method, this should happen automatically - * if you pass the Query object to get_data or similar. - * - * To construct a query, create a new Query() object and begin populating it with the various classes - * that define the various aspects of the query. - * - * Notes: - * - You do not have to specify things in any particular order, provided you specify all required - * components. - * - With database tables you do not have to specify your db prefix, this will be added automatically. - * - When constructing your query keep an eye on the error log - any problems will get spit out here. - * Note also that __toString won't let you throw Exceptions (!!!) so these are caught and echoed to - * the log instead. - * - * Here is an example of a select query which requests some data out of the entities table with an - * order and limit that uses a subset where and some normal where queries: - * - * <blockquote> - * // Construct the query - * $query = new Query(); - * - * // Say which table we're interested in - * $query->addTable(new TableQueryComponent("entities")); - * - * // What fields are we interested in - * $query->addSelectField(new SelectFieldQueryComponent("entities","*")); - * - * // Add access control (Default access control uses default fields on entities table. - * // Note that it will error without something specified here! - * $query->setAccessControl(new AccessControlQueryComponent()); - * - * // Set a limit and offset, may be omitted. - * $query->setLimitAndOffset(new LimitOffsetQueryComponent(10,0)); - * - * // Specify the order, may be omitted - * $query->setOrder(new OrderQueryComponent("entities", "subtype", "desc")); - * - * // Construct a where query - * // - * // This demonstrates a WhereSet which lets you have sub wheres, a - * // WhereStatic which lets you compare a table field against a value and a - * // Where which lets you compare a table/field with another table/field. - * $query->addWhere( - * new WhereSetQueryComponent( - * array( - * new WhereStaticQueryComponent("entities", "subtype","=", 1), - * new WhereQueryComponent("entities","subtype","=", "entities", "subtype") - * ) - * ) - * ); - * - * get_data($query); - * </blockquote> - * - * @author Curverider Ltd - */ - class Query - { - - /// The limit of the query - private $limit_and_offset; - - /// Fields to return on a query - private $fields; - - /// Tables to use in a from query - private $tables; - - /// Join tables - private $joins; - - /// Set values - private $sets; - - /// Where query - private $where; - - /// Order by - private $order; - - /// The query type - private $query_type; - - /// ACL - private $access_control; - - /** - * Construct query & initialise variables - */ - function __construct() - { - $this->fields = array(); - $this->tables = array(); - $this->joins = array(); - $this->where = array(); - $this->sets = array(); - - $this->setQueryType(new SelectQueryTypeQueryComponent()); - } - - /** - * Add limits and offsets to the query. - * - * @param LimitOffsetQueryComponent $component The limit and offset. - */ - public function setLimitAndOffset(LimitOffsetQueryComponent $component) { $this->limit_and_offset = $component; } - - /** - * Reset and set the field to the select statement. - * - * @param SelectFieldQueryComponent $component Table and field component. - */ - public function setSelectField(SelectFieldQueryComponent $component) - { - $this->fields = array(); - return $this->addSelectField($component); - } - - /** - * Add a select field. - * - * @param SelectFieldQueryComponent $component Add a component. - */ - public function addSelectField(SelectFieldQueryComponent $component) { $this->fields[] = $component; } - - /** - * Add a join to the component. - * - * @param JoinQueryComponent $component The join. - */ - public function addJoin(JoinQueryComponent $component) { $this->joins[] = $component; } - - /** - * Set a field value in an update or insert statement. - * - * @param SetQueryComponent $component Fields to set. - */ - public function addSet(SetQueryComponent $component) { $this->sets[] = $component; } - - /** - * Set the query type, i.e. "select", "update", "insert" & "delete". - * - * @param QueryTypeQueryComponent $component The query type. - */ - public function setQueryType(QueryTypeQueryComponent $component) { $this->query_type = $component; } - - /** - * Attach an order component. - * - * @param OrderQueryComponent $component The order component. - */ - public function setOrder(OrderQueryComponent $component) { $this->order = $component; } - - /** - * Add a table to the query. - * - * @param TableQueryComponent $component Table to add. - */ - public function addTable(TableQueryComponent $component) { $this->tables[] = $component; } - - /** - * Add a where clause to the query. - * - * @param WhereQueryComponent $component The where component - */ - public function addWhere(WhereQueryComponent $component) { $this->where[] = $component; } - - /** - * Set access control. - * - * @param AccessControlQueryComponent $component Access control. - */ - public function setAccessControl(AccessControlQueryComponent $component) { $this->access_control = $component; } - - public function __toString() - { - global $CONFIG; - - $sql = ""; - - try - { - // Query prefix & fields - if (!empty($this->query_type)) - { - $sql .= "{$this->query_type} "; - - if (!empty($this->fields)) - { - $fields = ""; - - foreach ($this->fields as $field) - $fields .= "$field"; - - $sql .= " $fields from "; - } - else - throw new DatabaseException(elgg_echo('DatabaseException:SelectFieldsMissing')); - } - else - throw new DatabaseException(elgg_echo('DatabaseException:UnspecifiedQueryType')); - - // Tables - if (!empty($this->tables)) - { - foreach($this->tables as $table) - $sql .= "$table, "; - - $sql = trim($sql, ", "); - } - else - throw new DatabaseException(elgg_echo('DatabaseException:NoTablesSpecified')); - - // Joins on select queries - if ($this->query_type->query_type == 'select') - { - if (!empty($this->joins)) - { - foreach($this->joins as $join) - $sql .= "$join "; - } - } - - // Setting values - if ( - ($this->query_type->query_type == 'update') || - ($this->query_type->query_type == 'insert') - ) - { - $sql .= "set "; - - foreach ($this->sets as $set) - $sql .= "$set, "; - - $sql = trim($sql, ", ") . " "; - } - - // Where - if (!empty($this->where)) - { - $sql .= " where 1 "; - - foreach ($this->where as $where) - $sql .= "$where "; - } - - // Access control - if (!empty($this->access_control)) - { - - // Catch missing Where - if (empty($this->where)) - $sql .= " where 1 "; - - $sql .= "{$this->access_control} "; - } - else - throw new DatabaseException(elgg_echo('DatabaseException:NoACL')); - - // Order by - if (!empty($this->order)) - $sql .= "{$this->order} "; - - // Limits - if (!empty($this->limit_and_offset)) - $sql .= "{$this->limit_and_offset} "; - - - - } catch (Exception $e) { - trigger_error($e, E_USER_WARNING); - } - - - return $sql; - } - - } - - /** - * @class SimpleQuery A wrapper for Query which provides simple interface for common functions. - * - * This class provides simple interface functions for constructing a (reasonably) standard database - * query. - * - * The constructor for this class sets a number of defaults, for example sets default access controls - * and a limit and offset - to change this then set it manually. - * - * @author Curverider Ltd - * @see Query - */ - class SimpleQuery extends Query - { - function __construct() - { - parent::__construct(); - - // Set a default query type (select) - $this->simpleQueryType(); - - // Set a default access control - $this->simpleAccessControl(); - - // Set default limit and offset - $this->simpleLimitAndOffset(); - } - - /** - * Set the query type. - * - * @param string $type The type of search - available are "select", "update", "delete", "insert". - */ - public function simpleQueryType($type = "select") - { - $type = strtolower(sanitise_string($type)); - - switch ($type) - { - case "insert" : - return $this->setQueryType(InsertQueryTypeQueryComponent()); - break; - case "delete" : - return $this->setQueryType(DeleteQueryTypeQueryComponent()); - break; - case "update" : - return $this->setQueryType(UpdateQueryTypeQueryComponent()); - break; - default: return $this->setQueryType(SelectQueryTypeQueryComponent()); - } - } - - /** - * Set a field to query in a select statement. - * - * @param string $table Table to query. - * @param string $field Field in that table. - */ - public function simpleSelectField($table, $field) { return $this->setSelectField(new SelectFieldQueryComponent($table, $field)); } - - /** - * Add a select field to query in a select statement. - * - * @param string $table Table to query. - * @param string $field Field in that table. - */ - public function simpleAddSelectField($table, $field) { return $this->addSelectField(new SelectFieldQueryComponent($table, $field)); } - - /** - * Add a set value to an update query. - * - * @param string $table The table to update. - * @param string $field The field in the table. - * @param mixed $value The value to set it to. - */ - public function simpleSet($table, $field, $value) { return $this->addSet(new SetQueryComponent($table, $field, $value)); } - - /** - * Add a join to the table. - * - * @param string $table Table one to join... - * @param string $field Field 1 with... - * @param string $table2 Table 2 ... - * @param string $field2 Field... - * @param string $operator Using this operator - */ - public function simpleJoin($table1, $field1, $table2, $field2, $operator = "=") { return $this->addJoin(new JoinQueryComponent($table1, $field1, $table2, $field2, $operator)); } - - /** - * Add a table to the query. - * - * @param string $table The table. - */ - public function simpleTable($table) { return $this->addTable(new TableQueryComponent($table)); } - - /** - * Compare one table/field to another table/field. - * - * @param string $left_table The table on the left of the operator - * @param string $left_field The left field - * @param string $operator The operator eg "=" or "<" - * @param string $right_table The table on the right of the operator - * @param string $right_field The right field - * @param string $link_operator How this where clause links with the previous clause, eg. "and" "or" - */ - public function simpleWhereOnTable($left_table, $left_field, $operator, $right_table, $right_field, $link_operator = "and") { return $this->addWhere(new WhereQueryComponent($left_table, $left_field, $operator, $right_table, $right_field, $link_operator)); } - - /** - * Compare one table/field to a value. - * - * @param string $left_table The table on the left of the operator - * @param string $left_field The left field - * @param string $operator The operator eg "=" or "<" - * @param string $value The value - * @param string $link_operator How this where clause links with the previous clause, eg. "and" "or" - */ - public function simpleWhereOnValue($left_table, $left_field, $operator, $value, $link_operator = "and") { return $this->addWhere(new WhereStaticQueryComponent($left_table, $left_field, $operator, $value, $link_operator)); } - - /** - * Set access control. - * - * @param string $acl_table The table where the access control field is. - * @param string $acl_field The field containing the access control. - * @param string $object_owner_id_field The field in $object_owner_table containing the owner information. - */ - public function simpleAccessControl($acl_table = "entities", $acl_field = "access_id", $object_owner_id_field = "owner_guid") { return $this->setAccessControl(new AccessControlQueryComponent($acl_table, $acl_field, $acl_table, $object_owner_id_field)); } - - /** - * Set the limit and offset. - * - * @param int $limit The limit. - * @param int $offset The offset. - */ - public function simpleLimitAndOffset($limit = 25, $offset = 0) { return $this->setLimitAndOffset(new LimitOffsetQueryComponent($limit, $offset)); } - - /** - * Set the order query. - * - * @param string $table The table to query - * @param string $field The field to query - * @param string $order Order the query - */ - public function simpleOrder($table, $field, $order = "desc") - { - $table = sanitise_string($table); - $field = sanitise_string($field); - $order = strtolower(sanitise_string($order)); - - return $this->setOrder(new OrderQueryComponent($table, $field, $order)); break; - } - } diff --git a/engine/lib/relationships.php b/engine/lib/relationships.php index c67596837..b0cd627fc 100644 --- a/engine/lib/relationships.php +++ b/engine/lib/relationships.php @@ -3,304 +3,17 @@ * Elgg relationships. * Stub containing relationship functions, making import and export easier. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage DataModel.Relationship */ /** - * Relationship class. - * - * @author Curverider Ltd - * @package Elgg - * @subpackage Core - */ -class ElggRelationship implements - Importable, - Exportable, - Loggable, // Can events related to this object class be logged - Iterator, // Override foreach behaviour - ArrayAccess // Override for array access - { - /** - * This contains the site's main properties (id, etc) - * @var array - */ - protected $attributes; - - /** - * Construct a new site object, optionally from a given id value or row. - * - * @param mixed $id - */ - function __construct($id = null) { - $this->attributes = array(); - - if (!empty($id)) { - if ($id instanceof stdClass) { - $relationship = $id; // Create from db row - } else { - $relationship = get_relationship($id); - } - - if ($relationship) { - $objarray = (array) $relationship; - foreach($objarray as $key => $value) { - $this->attributes[$key] = $value; - } - } - } - } - - /** - * Class member get overloading - * - * @param string $name - * @return mixed - */ - function __get($name) { - if (isset($this->attributes[$name])) { - return $this->attributes[$name]; - } - - return null; - } - - /** - * Class member set overloading - * - * @param string $name - * @param mixed $value - * @return mixed - */ - function __set($name, $value) { - $this->attributes[$name] = $value; - return true; - } - - /** - * Save the relationship - * - * @return int the relationship id - */ - public function save() { - if ($this->id > 0) { - delete_relationship($this->id); - } - - $this->id = add_entity_relationship($this->guid_one, $this->relationship, $this->guid_two); - if (!$this->id) { - throw new IOException(sprintf(elgg_new('IOException:UnableToSaveNew'), get_class())); - } - - return $this->id; - } - - /** - * Delete a given relationship. - */ - public function delete() { - return delete_relationship($this->id); - } - - /** - * Get a URL for this relationship. - * - * @return string - */ - public function getURL() { - return get_relationship_url($this->id); - } - - // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// - - /** - * Return an array of fields which can be exported. - */ - public function getExportableValues() { - return array( - 'id', - 'guid_one', - 'relationship', - 'guid_two' - ); - } - - /** - * Export this relationship - * - * @return array - */ - public function export() { - $uuid = get_uuid_from_object($this); - $relationship = new ODDRelationship( - guid_to_uuid($this->guid_one), - $this->relationship, - guid_to_uuid($this->guid_two) - ); - - $relationship->setAttribute('uuid', $uuid); - - return $relationship; - } - - // IMPORTABLE INTERFACE //////////////////////////////////////////////////////////// - - /** - * Import a relationship - * - * @param array $data - * @param int $version - * @return ElggRelationship - * @throws ImportException - */ - public function import(ODD $data) { - if (!($element instanceof ODDRelationship)) { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnexpectedODDClass')); - } - - $uuid_one = $data->getAttribute('uuid1'); - $uuid_two = $data->getAttribute('uuid2'); - - // See if this entity has already been imported, if so then we need to link to it - $entity1 = get_entity_from_uuid($uuid_one); - $entity2 = get_entity_from_uuid($uuid_two); - if (($entity1) && ($entity2)) { - // Set the item ID - $this->attributes['guid_one'] = $entity1->getGUID(); - $this->attributes['guid_two'] = $entity2->getGUID(); - - // Map verb to relationship - //$verb = $data->getAttribute('verb'); - //$relationship = get_relationship_from_verb($verb); - $relationship = $data->getAttribute('type'); - - if ($relationship) { - $this->attributes['relationship'] = $relationship; - // save - $result = $this->save(); - if (!$result) { - throw new ImportException(sprintf(elgg_echo('ImportException:ProblemSaving'), get_class())); - } - - return $this; - } - } - } - - // 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->id; - } - - /** - * 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_relationship($id); - } - - /** - * Return the GUID of the owner of this object. - */ - public function getObjectOwnerGUID() { - return $this->owner_guid; - } - - /** - * Return a type of the object - eg. object, group, user, relationship, metadata, annotation etc - */ - public function getType() { - return 'relationship'; - } - - /** - * Return a subtype. For metadata & annotations this is the 'name' and for relationship this is the relationship type. - */ - public function getSubtype() { - return $this->relationship; - } - - // ITERATOR INTERFACE ////////////////////////////////////////////////////////////// - /* - * This lets an entity's attributes be displayed using foreach as a normal array. - * Example: http://www.sitepoint.com/print/php5-standard-library - */ - - private $valid = FALSE; - - function rewind() { - $this->valid = (FALSE !== reset($this->attributes)); - } - - function current() { - return current($this->attributes); - } - - function key() { - return key($this->attributes); - } - - function next() { - $this->valid = (FALSE !== next($this->attributes)); - } - - function valid() { - return $this->valid; - } - - // ARRAY ACCESS INTERFACE ////////////////////////////////////////////////////////// - /* - * This lets an entity's attributes be accessed like an associative array. - * Example: http://www.sitepoint.com/print/php5-standard-library - */ - - function offsetSet($key, $value) { - if ( array_key_exists($key, $this->attributes) ) { - $this->attributes[$key] = $value; - } - } - - function offsetGet($key) { - if ( array_key_exists($key, $this->attributes) ) { - return $this->attributes[$key]; - } - } - - function offsetUnset($key) { - if ( array_key_exists($key, $this->attributes) ) { - $this->attributes[$key] = ""; // Full unsetting is dangerious for our objects - } - } - - function offsetExists($offset) { - return array_key_exists($offset, $this->attributes); - } -} - - -/** * Convert a database row to a new ElggRelationship * - * @param stdClass $row - * @return stdClass or ElggMetadata + * @param stdClass $row Database row from the relationship table + * + * @return ElggRelationship|stdClass + * @access private */ function row_to_elggrelationship($row) { if (!($row instanceof stdClass)) { @@ -313,20 +26,25 @@ function row_to_elggrelationship($row) { /** * Return a relationship. * - * @param int $id + * @param int $id The ID of a relationship + * + * @return ElggRelationship|false */ function get_relationship($id) { global $CONFIG; $id = (int)$id; - return row_to_elggrelationship(get_data_row("SELECT * from {$CONFIG->dbprefix}entity_relationships where id=$id")); + $query = "SELECT * from {$CONFIG->dbprefix}entity_relationships where id=$id"; + return row_to_elggrelationship(get_data_row($query)); } /** * Delete a specific relationship. * - * @param int $id + * @param int $id The relationship ID + * + * @return bool */ function delete_relationship($id) { global $CONFIG; @@ -335,7 +53,7 @@ function delete_relationship($id) { $relationship = get_relationship($id); - if (trigger_elgg_event('delete', 'relationship', $relationship)) { + if (elgg_trigger_event('delete', 'relationship', $relationship)) { return delete_data("delete from {$CONFIG->dbprefix}entity_relationships where id=$id"); } @@ -348,9 +66,11 @@ function delete_relationship($id) { * * This function lets you make the statement "$guid_one is a $relationship of $guid_two". * - * @param int $guid_one - * @param string $relationship - * @param int $guid_two + * @param int $guid_one First GUID + * @param string $relationship Relationship name + * @param int $guid_two Second GUID + * + * @return bool */ function add_entity_relationship($guid_one, $relationship, $guid_two) { global $CONFIG; @@ -358,17 +78,20 @@ function add_entity_relationship($guid_one, $relationship, $guid_two) { $guid_one = (int)$guid_one; $relationship = sanitise_string($relationship); $guid_two = (int)$guid_two; + $time = time(); // Check for duplicates if (check_entity_relationship($guid_one, $relationship, $guid_two)) { return false; } - $result = insert_data("INSERT into {$CONFIG->dbprefix}entity_relationships (guid_one, relationship, guid_two) values ($guid_one, '$relationship', $guid_two)"); + $result = insert_data("INSERT into {$CONFIG->dbprefix}entity_relationships + (guid_one, relationship, guid_two, time_created) + values ($guid_one, '$relationship', $guid_two, $time)"); - if ($result!==false) { + if ($result !== false) { $obj = get_relationship($result); - if (trigger_elgg_event('create', $relationship, $obj)) { + if (elgg_trigger_event('create', $relationship, $obj)) { return true; } else { delete_relationship($result); @@ -379,12 +102,14 @@ function add_entity_relationship($guid_one, $relationship, $guid_two) { } /** - * Determine whether or not a relationship between two entities exists and returns the relationship object if it does + * Determine if a relationship between two entities exists + * and returns the relationship object if it does * - * @param int $guid_one The GUID of the entity "owning" the relationship + * @param int $guid_one The GUID of the entity "owning" the relationship * @param string $relationship The type of relationship - * @param int $guid_two The GUID of the entity the relationship is with - * @return object|false Depending on success + * @param int $guid_two The GUID of the entity the relationship is with + * + * @return ElggRelationship|false Depending on success */ function check_entity_relationship($guid_one, $relationship, $guid_two) { global $CONFIG; @@ -393,7 +118,13 @@ function check_entity_relationship($guid_one, $relationship, $guid_two) { $relationship = sanitise_string($relationship); $guid_two = (int)$guid_two; - if ($row = get_data_row("SELECT * FROM {$CONFIG->dbprefix}entity_relationships WHERE guid_one=$guid_one AND relationship='$relationship' AND guid_two=$guid_two limit 1")) { + $query = "SELECT * FROM {$CONFIG->dbprefix}entity_relationships + WHERE guid_one=$guid_one + AND relationship='$relationship' + AND guid_two=$guid_two limit 1"; + + $row = row_to_elggrelationship(get_data_row($query)); + if ($row) { return $row; } @@ -403,9 +134,11 @@ function check_entity_relationship($guid_one, $relationship, $guid_two) { /** * Remove an arbitrary relationship between two entities. * - * @param int $guid_one - * @param string $relationship - * @param int $guid_two + * @param int $guid_one First GUID + * @param string $relationship Relationship name + * @param int $guid_two Second GUID + * + * @return bool */ function remove_entity_relationship($guid_one, $relationship, $guid_two) { global $CONFIG; @@ -419,8 +152,13 @@ function remove_entity_relationship($guid_one, $relationship, $guid_two) { return false; } - if (trigger_elgg_event('delete', $relationship, $obj)) { - return delete_data("DELETE from {$CONFIG->dbprefix}entity_relationships where guid_one=$guid_one and relationship='$relationship' and guid_two=$guid_two"); + if (elgg_trigger_event('delete', $relationship, $obj)) { + $query = "DELETE from {$CONFIG->dbprefix}entity_relationships + where guid_one=$guid_one + and relationship='$relationship' + and guid_two=$guid_two"; + + return (bool)delete_data($query); } else { return false; } @@ -429,11 +167,12 @@ function remove_entity_relationship($guid_one, $relationship, $guid_two) { /** * Removes all arbitrary relationships originating from a particular entity * - * @param int $guid_one The GUID of the entity - * @param string $relationship The name of the relationship (optionally) - * @param true|false $inverse Whether we're deleting inverse relationships (default false) - * @param string $type The type of entity to limit this relationship delete to (defaults to all) - * @return true|false Depending on success + * @param int $guid_one The GUID of the entity + * @param string $relationship The name of the relationship (optional) + * @param bool $inverse Whether we're deleting inverse relationships (default false) + * @param string $type The type of entity to the delete to (defaults to all) + * + * @return bool Depending on success */ function remove_entity_relationships($guid_one, $relationship = "", $inverse = false, $type = '') { global $CONFIG; @@ -461,10 +200,16 @@ function remove_entity_relationships($guid_one, $relationship = "", $inverse = f } if (!$inverse) { - $sql = "DELETE er from {$CONFIG->dbprefix}entity_relationships as er {$join} where guid_one={$guid_one} {$where}"; + $sql = "DELETE er from {$CONFIG->dbprefix}entity_relationships as er + {$join} + where guid_one={$guid_one} {$where}"; + return delete_data($sql); } else { - $sql = "DELETE er from {$CONFIG->dbprefix}entity_relationships as er {$join} where guid_two={$guid_one} {$where}"; + $sql = "DELETE er from {$CONFIG->dbprefix}entity_relationships as er + {$join} where + guid_two={$guid_one} {$where}"; + return delete_data($sql); } } @@ -472,7 +217,10 @@ function remove_entity_relationships($guid_one, $relationship = "", $inverse = f /** * Get all the relationships for a given guid. * - * @param int $guid + * @param int $guid The GUID of the relationship owner + * @param bool $inverse_relationship Inverse relationship owners? + * + * @return ElggRelationship[] */ function get_entity_relationships($guid, $inverse_relationship = FALSE) { global $CONFIG; @@ -486,9 +234,22 @@ function get_entity_relationships($guid, $inverse_relationship = FALSE) { return get_data($query, "row_to_elggrelationship"); } - /** * Return entities matching a given query joining against a relationship. + * Also accepts all options available to elgg_get_entities() and + * elgg_get_entities_from_metadata(). + * + * To ask for entities that do not have a particulat relationship to an entity, + * use a custom where clause like the following: + * + * $options['wheres'][] = "NOT EXISTS ( + * SELECT 1 FROM {$db_prefix}entity_relationships + * WHERE guid_one = e.guid + * AND relationship = '$relationship' + * )"; + * + * @see elgg_get_entities + * @see elgg_get_entities_from_metadata * * @param array $options Array in format: * @@ -498,7 +259,8 @@ function get_entity_relationships($guid, $inverse_relationship = FALSE) { * * inverse_relationship => BOOL Inverse the relationship * - * @return array + * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors. + * @since 1.7.0 */ function elgg_get_entities_from_relationship($options) { $defaults = array( @@ -509,7 +271,7 @@ function elgg_get_entities_from_relationship($options) { $options = array_merge($defaults, $options); - $clauses = elgg_get_entity_relationship_where_sql('e', $options['relationship'], + $clauses = elgg_get_entity_relationship_where_sql('e.guid', $options['relationship'], $options['relationship_guid'], $options['inverse_relationship']); if ($clauses) { @@ -530,6 +292,16 @@ function elgg_get_entities_from_relationship($options) { } $options['joins'] = array_merge($options['joins'], $clauses['joins']); + + if (isset($options['selects']) && !is_array($options['selects'])) { + $options['selects'] = array($options['selects']); + } elseif (!isset($options['selects'])) { + $options['selects'] = array(); + } + + $select = array('r.id'); + + $options['selects'] = array_merge($options['selects'], $select); } return elgg_get_entities_from_metadata($options); @@ -540,13 +312,20 @@ function elgg_get_entities_from_relationship($options) { * * @todo add support for multiple relationships and guids. * - * @param $table Entities table name - * @param $relationship relationship string - * @param $entity_guid entity guid to check + * @param string $column Column name the guid should be checked against. + * Provide in table.column format. + * @param string $relationship Relationship string + * @param int $relationship_guid Entity guid to check + * @param bool $inverse_relationship Inverse relationship check? + * * @return mixed + * @since 1.7.0 + * @access private */ -function elgg_get_entity_relationship_where_sql($table, $relationship = NULL, $relationship_guid = NULL, $inverse_relationship = FALSE) { - if ($relationship == NULL && $entity_guid == NULL) { +function elgg_get_entity_relationship_where_sql($column, $relationship = NULL, +$relationship_guid = NULL, $inverse_relationship = FALSE) { + + if ($relationship == NULL && $relationship_guid == NULL) { return ''; } @@ -556,11 +335,9 @@ function elgg_get_entity_relationship_where_sql($table, $relationship = NULL, $r $joins = array(); if ($inverse_relationship) { - $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_one = e.guid"; - //$wheres[] = "{$table}.guid = {$CONFIG->dbprefix}entity_relationships.guid_two"; + $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_one = $column"; } else { - $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_two = e.guid"; - //$wheres[] = "{$table}.guid = {$CONFIG->dbprefix}entity_relationships.guid_one"; + $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_two = $column"; } if ($relationship) { @@ -575,7 +352,7 @@ function elgg_get_entity_relationship_where_sql($table, $relationship = NULL, $r } } - if ($where_str = implode(' AND ' , $wheres)) { + if ($where_str = implode(' AND ', $wheres)) { return array('wheres' => array("($where_str)"), 'joins' => $joins); } @@ -584,302 +361,62 @@ function elgg_get_entity_relationship_where_sql($table, $relationship = NULL, $r } /** - * @deprecated 1.7. Use elgg_get_entities_from_relationship(). - * @param $relationship - * @param $relationship_guid - * @param $inverse_relationship - * @param $type - * @param $subtype - * @param $owner_guid - * @param $order_by - * @param $limit - * @param $offset - * @param $count - * @param $site_guid - * @return unknown_type - */ -function get_entities_from_relationship($relationship, $relationship_guid, $inverse_relationship = false, -$type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, -$count = false, $site_guid = 0) { - - elgg_deprecated_notice('get_entities_from_relationship() was deprecated by elgg_get_entities_from_relationship()!', 1.7); - - $options = array(); - - $options['relationship'] = $relationship; - $options['relationship_guid'] = $relationship_guid; - $options['inverse_relationship'] = $inverse_relationship; - - if ($type) { - $options['types'] = $type; - } - - if ($subtype) { - $options['subtypes'] = $subtype; - } - - if ($owner_guid) { - $options['owner_guid'] = $owner_guid; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } - - if ($order_by) { - $options['order_by']; - } - - if ($site_guid) { - $options['site_guid']; - } - - if ($count) { - $options['count'] = $count; - } - - return elgg_get_entities_from_relationship($options); -} - -/** * Returns a viewable list of entities by relationship * - * @see elgg_view_entity_list + * @param array $options Options array for retrieval of entities + * + * @see elgg_list_entities() + * @see elgg_get_entities_from_relationship() * - * @param string $relationship The relationship eg "friends_of" - * @param int $relationship_guid The guid of the entity to use query - * @param bool $inverse_relationship Reverse the normal function of the query to instead say "give me all entities for whome $relationship_guid is a $relationship of" - * @param string $type The type of entity (eg 'object') - * @param string $subtype The entity subtype - * @param int $owner_guid The owner (default: all) - * @param int $limit The number of entities to display on a page - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow gallery view - * @param true|false $pagination Whether to display pagination (default: true) * @return string The viewable list of entities */ -function list_entities_from_relationship($relationship, $relationship_guid, $inverse_relationship = false, $type = ELGG_ENTITIES_ANY_VALUE, $subtype = ELGG_ENTITIES_ANY_VALUE, $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false, $pagination = true) { - $limit = (int) $limit; - $offset = (int) get_input('offset'); - $options = array( - 'relationship' => $relationship, - 'relationship_guid' => $relationship_guid, - 'inverse_relationship' => $inverse_relationship, - 'types' => $type, - 'subtypes' => $subtype, - 'owner_guid' => $owner_guid, - 'order_by' => '', - 'limit' => $limit, - 'offset' => $offset, - 'count' => TRUE - ); - $count = elgg_get_entities_from_relationship($options); - $options['count'] = FALSE; - $entities = elgg_get_entities_from_relationship($options); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); +function elgg_list_entities_from_relationship(array $options = array()) { + return elgg_list_entities($options, 'elgg_get_entities_from_relationship'); } /** * Gets the number of entities by a the number of entities related to them in a particular way. - * This is a good way to get out the users with the most friends, or the groups with the most members. + * This is a good way to get out the users with the most friends, or the groups with the + * most members. * - * @param string $relationship The relationship eg "friends_of" - * @param bool $inverse_relationship Reverse the normal function of the query to instead say "give me all entities for whome $relationship_guid is a $relationship of" (default: true) - * @param string $type The type of entity (default: all) - * @param string $subtype The entity subtype (default: all) - * @param int $owner_guid The owner of the entities (default: none) - * @param int $limit - * @param int $offset - * @param boolean $count Set to true if you want to count the number of entities instead (default false) - * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. - * @return array|int|false An array of entities, or the number of entities, or false on failure + * @param array $options An options array compatible with + * elgg_get_entities_from_relationship() + * @return ElggEntity[]|mixed int If count, int. If not count, array. false on errors. + * @since 1.8.0 */ - -function get_entities_by_relationship_count($relationship, $inverse_relationship = true, $type = "", $subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { - global $CONFIG; - - $relationship = sanitise_string($relationship); - $inverse_relationship = (bool)$inverse_relationship; - $type = sanitise_string($type); - if ($subtype AND !$subtype = get_subtype_id($type, $subtype)) { - return false; - } - $owner_guid = (int)$owner_guid; - $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; - } - - //$access = get_access_list(); - - $where = array(); - - if ($relationship!="") { - $where[] = "r.relationship='$relationship'"; - } - - if ($inverse_relationship) { - $on = 'e.guid = r.guid_two'; - } else { - $on = 'e.guid = r.guid_one'; - } - if ($type != "") { - $where[] = "e.type='$type'"; - } - - if ($subtype) { - $where[] = "e.subtype=$subtype"; - } - - if ($owner_guid != "") { - $where[] = "e.container_guid='$owner_guid'"; - } - - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - - if ($count) { - $query = "SELECT count(distinct e.guid) as total "; - } else { - $query = "SELECT e.*, count(e.guid) as total "; - } - - $query .= " from {$CONFIG->dbprefix}entity_relationships r JOIN {$CONFIG->dbprefix}entities e on {$on} where "; - - if (!empty($where)) { - foreach ($where as $w) { - $query .= " $w and "; - } - } - $query .= get_access_sql_suffix("e"); // Add access controls - - if (!$count) { - $query .= " group by e.guid "; - $query .= " order by total desc limit {$offset}, {$limit}"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } - } - - return false; +function elgg_get_entities_from_relationship_count(array $options = array()) { + $options['selects'][] = "COUNT(e.guid) as total"; + $options['group_by'] = 'r.guid_two'; + $options['order_by'] = 'total desc'; + return elgg_get_entities_from_relationship($options); } /** - * Displays a human-readable list of entities + * Returns a list of entities by relationship count * - * @param string $relationship The relationship eg "friends_of" - * @param bool $inverse_relationship Reverse the normal function of the query to instead say "give me all entities for whome $relationship_guid is a $relationship of" (default: true) - * @param string $type The type of entity (eg 'object') - * @param string $subtype The entity subtype - * @param int $owner_guid The owner (default: all) - * @param int $limit The number of entities to display on a page - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow gallery view - * @param true|false $pagination Whether to display pagination (default: true) - * @return string The viewable list of entities - */ - -function list_entities_by_relationship_count($relationship, $inverse_relationship = true, $type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = false, $pagination = true) { - $limit = (int) $limit; - $offset = (int) get_input('offset'); - $count = get_entities_by_relationship_count($relationship,$inverse_relationship,$type,$subtype,$owner_guid,0,0,true); - $entities = get_entities_by_relationship_count($relationship,$inverse_relationship,$type,$subtype,$owner_guid,$limit,$offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); -} - -/** - * Gets the number of entities by a the number of entities related to them in a particular way also constrained by - * metadata + * @see elgg_get_entities_from_relationship_count() * - * @param string $relationship The relationship eg "friends_of" - * @param int $relationship_guid The guid of the entity to use query - * @param bool $inverse_relationship Reverse the normal function of the query to instead say "give me all entities for whome $relationship_guid is a $relationship of" (default: true) - * @param String $meta_name The metadata name - * @param String $meta_value The metadata value - * @param string $type The type of entity (default: all) - * @param string $subtype The entity subtype (default: all) - * @param int $owner_guid The owner of the entities (default: none) - * @param int $limit - * @param int $offset - * @param boolean $count Set to true if you want to count the number of entities instead (default false) - * @param int $site_guid The site to get entities for. Leave as 0 (default) for the current site; -1 for all sites. - * @return array|int|false An array of entities, or the number of entities, or false on failure + * @param array $options Options array + * + * @return string + * @since 1.8.0 */ -function get_entities_from_relationships_and_meta($relationship, $relationship_guid, $inverse_relationship = false, $meta_name = "", $meta_value = "", $type = "", $subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { - elgg_deprecated_notice('get_entities_from_relationship_and_meta() was deprecated by elgg_get_entities_from_relationship()!', 1.7); - - $options = array(); - - $options['relationship'] = $relationship; - $options['relationship_guid'] = $relationship_guid; - $options['inverse_relationship'] = $inverse_relationship; - - if ($meta_value) { - $options['values'] = $meta_value; - } - - if ($entity_type) { - $options['types'] = $entity_type; - } - - if ($type) { - $options['types'] = $type; - } - - if (subtype) { - $options['subtypes'] = $subtype; - } - - if ($owner_guid) { - $options['owner_guid'] = $owner_guid; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } - - if ($order_by) { - $options['order_by']; - } - - if ($site_guid) { - $options['site_guid']; - } - - if ($count) { - $options['count'] = $count; - } - - return elgg_get_entities_from_relationship($options); +function elgg_list_entities_from_relationship_count($options) { + return elgg_list_entities($options, 'elgg_get_entities_from_relationship_count'); } /** * Sets the URL handler for a particular relationship type * - * @param string $function_name The function to register * @param string $relationship_type The relationship type. - * @return true|false Depending on success + * @param string $function_name The function to register + * + * @return bool Depending on success */ -function register_relationship_url_handler($function_name, $relationship_type = "all") { +function elgg_register_relationship_url_handler($relationship_type, $function_name) { global $CONFIG; - if (!is_callable($function_name)) { + if (!is_callable($function_name, true)) { return false; } @@ -895,8 +432,9 @@ function register_relationship_url_handler($function_name, $relationship_type = /** * Get the url for a given relationship. * - * @param unknown_type $id - * @return unknown + * @param int $id Relationship ID + * + * @return string */ function get_relationship_url($id) { global $CONFIG; @@ -920,13 +458,13 @@ function get_relationship_url($id) { } if (is_callable($function)) { - $url = $function($relationship); + $url = call_user_func($function, $relationship); } if ($url == "") { $nameid = $relationship->id; - $url = $CONFIG->wwwroot . "export/$view/$guid/relationship/$nameid/"; + $url = elgg_get_site_url() . "export/$view/$guid/relationship/$nameid/"; } return $url; @@ -936,16 +474,19 @@ function get_relationship_url($id) { } /**** HELPER FUNCTIONS FOR RELATIONSHIPS OF TYPE 'ATTACHED' ****/ +// @todo what is this? /** * Function to determine if the object trying to attach to other, has already done so + * * @param int $guid_one This is the target object * @param int $guid_two This is the object trying to attach to $guid_one - * @return true | false - **/ - -function already_attached($guid_one, $guid_two){ - if ($attached = check_entity_relationship($guid_one, "attached", $guid_two)){ + * + * @return bool + * @access private + */ +function already_attached($guid_one, $guid_two) { + if ($attached = check_entity_relationship($guid_one, "attached", $guid_two)) { return true; } else { return false; @@ -954,37 +495,56 @@ function already_attached($guid_one, $guid_two){ /** * Function to get all objects attached to a particular object - * @param int $guid - * @param string $type - the type of object to return e.g. 'file', 'friend_of' etc - * @return an array of objects -**/ - -function get_attachments($guid, $type=""){ - $attached = elgg_get_entities_from_relationship(array('relationship' => 'attached', 'relationship_guid' => $guid, 'inverse_relationship' => $inverse_relationship = false, 'types' => $type, 'subtypes' => '', 'owner_guid' => 0, 'order_by' => 'time_created desc', 'limit' => 10, 'offset' => 0, 'count' => false, 'site_guid' => 0)); + * + * @param int $guid Entity GUID + * @param string $type The type of object to return e.g. 'file', 'friend_of' etc + * + * @return ElggEntity[] + * @access private + */ +function get_attachments($guid, $type = "") { + $options = array( + 'relationship' => 'attached', + 'relationship_guid' => $guid, + 'inverse_relationship' => false, + 'type' => $type, + 'subtypes' => '', + 'owner_guid' => 0, + 'order_by' => 'time_created desc', + 'limit' => 10, + 'offset' => 0, + 'count' => false, + 'site_guid' => 0 + ); + $attached = elgg_get_entities_from_relationship($options); return $attached; } /** * Function to remove a particular attachment between two objects + * * @param int $guid_one This is the target object * @param int $guid_two This is the object to remove from $guid_one - * @return a view -**/ - -function remove_attachment($guid_one, $guid_two){ - if(already_attached($guid_one, $guid_two)) { + * + * @return void + * @access private + */ +function remove_attachment($guid_one, $guid_two) { + if (already_attached($guid_one, $guid_two)) { remove_entity_relationship($guid_one, "attached", $guid_two); } } /** * Function to start the process of attaching one object to another + * * @param int $guid_one This is the target object * @param int $guid_two This is the object trying to attach to $guid_one - * @return a view -**/ - -function make_attachment($guid_one, $guid_two){ + * + * @return true|void + * @access private + */ +function make_attachment($guid_one, $guid_two) { if (!(already_attached($guid_one, $guid_two))) { if (add_entity_relationship($guid_one, "attached", $guid_two)) { return true; @@ -993,7 +553,15 @@ function make_attachment($guid_one, $guid_two){ } /** - * Handler called by trigger_plugin_hook on the "import" event. + * Handler called by trigger_plugin_hook on the "import" event. + * + * @param string $hook import + * @param string $entity_type all + * @param mixed $returnvalue Value from previous hook + * @param mixed $params Array of params + * + * @return mixed + * @access private */ function import_relationship_plugin_hook($hook, $entity_type, $returnvalue, $params) { $element = $params['element']; @@ -1003,17 +571,24 @@ function import_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par if ($element instanceof ODDRelationship) { $tmp = new ElggRelationship(); $tmp->import($element); - - return $tmp; } + return $tmp; } /** - * Handler called by trigger_plugin_hook on the "export" event. + * Handler called by trigger_plugin_hook on the "export" event. + * + * @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 + * @throws InvalidParameterException + * @access private */ function export_relationship_plugin_hook($hook, $entity_type, $returnvalue, $params) { - global $CONFIG; - // Sanity check values if ((!is_array($params)) && (!isset($params['guid']))) { throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); @@ -1037,36 +612,32 @@ function export_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par } /** - * An event listener which will notify users based on certain events. + * Notify user that someone has friended them + * + * @param string $event Event name + * @param string $type Object type + * @param mixed $object Object * - * @param unknown_type $event - * @param unknown_type $object_type - * @param unknown_type $object + * @return bool + * @access private */ -function relationship_notification_hook($event, $object_type, $object) { - global $CONFIG; - - if ( - ($object instanceof ElggRelationship) && - ($event == 'create') && - ($object_type == 'friend') - ) - { - $user_one = get_entity($object->guid_one); - $user_two = get_entity($object->guid_two); - - // Notify target user - return notify_user($object->guid_two, $object->guid_one, sprintf(elgg_echo('friend:newfriend:subject'), $user_one->name), - sprintf(elgg_echo("friend:newfriend:body"), $user_one->name, $CONFIG->site->url . "pg/profile/" . $user_one->username) - ); - } +function relationship_notification_hook($event, $type, $object) { + /* @var ElggRelationship $object */ + $user_one = get_entity($object->guid_one); + /* @var ElggUser $user_one */ + + return notify_user($object->guid_two, + $object->guid_one, + elgg_echo('friend:newfriend:subject', array($user_one->name)), + elgg_echo("friend:newfriend:body", array($user_one->name, $user_one->getURL())) + ); } -/** Register the import hook */ -register_plugin_hook("import", "all", "import_relationship_plugin_hook", 3); +// Register the import hook +elgg_register_plugin_hook_handler("import", "all", "import_relationship_plugin_hook", 3); -/** Register the hook, ensuring entities are serialised first */ -register_plugin_hook("export", "all", "export_relationship_plugin_hook", 3); +// Register the hook, ensuring entities are serialised first +elgg_register_plugin_hook_handler("export", "all", "export_relationship_plugin_hook", 3); -/** Register event to listen to some events **/ -register_elgg_event_handler('create','friend','relationship_notification_hook'); +// Register event to listen to some events +elgg_register_event_handler('create', 'friend', 'relationship_notification_hook'); diff --git a/engine/lib/river.php b/engine/lib/river.php new file mode 100644 index 000000000..e92040eb7 --- /dev/null +++ b/engine/lib/river.php @@ -0,0 +1,703 @@ +<?php +/** + * Elgg river. + * Activity stream functions. + * + * @package Elgg.Core + * @subpackage SocialModel.River + */ + +/** + * Adds an item to the river. + * + * @param string $view The view that will handle the river item (must exist) + * @param string $action_type An arbitrary string to define the action (eg 'comment', 'create') + * @param int $subject_guid The GUID of the entity doing the action + * @param int $object_guid The GUID of the entity being acted upon + * @param int $access_id The access ID of the river item (default: same as the object) + * @param int $posted The UNIX epoch timestamp of the river item (default: now) + * @param int $annotation_id The annotation ID associated with this river entry + * + * @return int/bool River ID or false on failure + */ +function add_to_river($view, $action_type, $subject_guid, $object_guid, $access_id = "", +$posted = 0, $annotation_id = 0) { + + global $CONFIG; + + // use default viewtype for when called from web services api + if (!elgg_view_exists($view, 'default')) { + return false; + } + if (!($subject = get_entity($subject_guid))) { + return false; + } + if (!($object = get_entity($object_guid))) { + return false; + } + if (empty($action_type)) { + return false; + } + if ($posted == 0) { + $posted = time(); + } + if ($access_id === "") { + $access_id = $object->access_id; + } + $type = $object->getType(); + $subtype = $object->getSubtype(); + + $view = sanitise_string($view); + $action_type = sanitise_string($action_type); + $subject_guid = sanitise_int($subject_guid); + $object_guid = sanitise_int($object_guid); + $access_id = sanitise_int($access_id); + $posted = sanitise_int($posted); + $annotation_id = sanitise_int($annotation_id); + + $values = array( + 'type' => $type, + 'subtype' => $subtype, + 'action_type' => $action_type, + 'access_id' => $access_id, + 'view' => $view, + 'subject_guid' => $subject_guid, + 'object_guid' => $object_guid, + 'annotation_id' => $annotation_id, + 'posted' => $posted, + ); + + // return false to stop insert + $values = elgg_trigger_plugin_hook('creating', 'river', null, $values); + if ($values == false) { + // inserting did not fail - it was just prevented + return true; + } + + extract($values); + + // Attempt to save river item; return success status + $id = insert_data("insert into {$CONFIG->dbprefix}river " . + " set type = '$type', " . + " subtype = '$subtype', " . + " action_type = '$action_type', " . + " access_id = $access_id, " . + " view = '$view', " . + " subject_guid = $subject_guid, " . + " object_guid = $object_guid, " . + " annotation_id = $annotation_id, " . + " posted = $posted"); + + // update the entities which had the action carried out on it + // @todo shouldn't this be down elsewhere? Like when an annotation is saved? + if ($id) { + update_entity_last_action($object_guid, $posted); + + $river_items = elgg_get_river(array('id' => $id)); + if ($river_items) { + elgg_trigger_event('created', 'river', $river_items[0]); + } + return $id; + } else { + return false; + } +} + +/** + * Delete river items + * + * @warning not checking access (should we?) + * + * @param array $options Parameters: + * ids => INT|ARR River item id(s) + * subject_guids => INT|ARR Subject guid(s) + * object_guids => INT|ARR Object guid(s) + * annotation_ids => INT|ARR The identifier of the annotation(s) + * action_types => STR|ARR The river action type(s) identifier + * views => STR|ARR River view(s) + * + * types => STR|ARR Entity type string(s) + * subtypes => STR|ARR Entity subtype string(s) + * type_subtype_pairs => ARR Array of type => subtype pairs where subtype + * can be an array of subtype strings + * + * posted_time_lower => INT The lower bound on the time posted + * posted_time_upper => INT The upper bound on the time posted + * + * @return bool + * @since 1.8.0 + */ +function elgg_delete_river(array $options = array()) { + global $CONFIG; + + $defaults = array( + 'ids' => ELGG_ENTITIES_ANY_VALUE, + + 'subject_guids' => ELGG_ENTITIES_ANY_VALUE, + 'object_guids' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_ids' => ELGG_ENTITIES_ANY_VALUE, + + 'views' => ELGG_ENTITIES_ANY_VALUE, + 'action_types' => ELGG_ENTITIES_ANY_VALUE, + + 'types' => ELGG_ENTITIES_ANY_VALUE, + 'subtypes' => ELGG_ENTITIES_ANY_VALUE, + 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE, + + 'posted_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'posted_time_upper' => ELGG_ENTITIES_ANY_VALUE, + + 'wheres' => array(), + 'joins' => array(), + + ); + + $options = array_merge($defaults, $options); + + $singulars = array('id', 'subject_guid', 'object_guid', 'annotation_id', 'action_type', 'view', 'type', 'subtype'); + $options = elgg_normalise_plural_options_array($options, $singulars); + + $wheres = $options['wheres']; + + $wheres[] = elgg_get_guid_based_where_sql('rv.id', $options['ids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']); + $wheres[] = elgg_river_get_action_where_sql($options['action_types']); + $wheres[] = elgg_river_get_view_where_sql($options['views']); + $wheres[] = elgg_get_river_type_subtype_where_sql('rv', $options['types'], + $options['subtypes'], $options['type_subtype_pairs']); + + if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) { + $wheres[] = "rv.posted >= {$options['posted_time_lower']}"; + } + + if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) { + $wheres[] = "rv.posted <= {$options['posted_time_upper']}"; + } + + // see if any functions failed + // remove empty strings on successful functions + foreach ($wheres as $i => $where) { + if ($where === FALSE) { + return FALSE; + } elseif (empty($where)) { + unset($wheres[$i]); + } + } + + // remove identical where clauses + $wheres = array_unique($wheres); + + $query = "DELETE rv.* FROM {$CONFIG->dbprefix}river rv "; + + // remove identical join clauses + $joins = array_unique($options['joins']); + + // add joins + foreach ($joins as $j) { + $query .= " $j "; + } + + // add wheres + $query .= ' WHERE '; + + foreach ($wheres as $w) { + $query .= " $w AND "; + } + $query .= "1=1"; + + return delete_data($query); +} + +/** + * Get river items + * + * @note If using types and subtypes in a query, they are joined with an AND. + * + * @param array $options Parameters: + * ids => INT|ARR River item id(s) + * subject_guids => INT|ARR Subject guid(s) + * object_guids => INT|ARR Object guid(s) + * annotation_ids => INT|ARR The identifier of the annotation(s) + * action_types => STR|ARR The river action type(s) identifier + * posted_time_lower => INT The lower bound on the time posted + * posted_time_upper => INT The upper bound on the time posted + * + * types => STR|ARR Entity type string(s) + * subtypes => STR|ARR Entity subtype string(s) + * type_subtype_pairs => ARR Array of type => subtype pairs where subtype + * can be an array of subtype strings + * + * relationship => STR Relationship identifier + * relationship_guid => INT|ARR Entity guid(s) + * inverse_relationship => BOOL Subject or object of the relationship (false) + * + * limit => INT Number to show per page (20) + * offset => INT Offset in list (0) + * count => BOOL Count the river items? (false) + * order_by => STR Order by clause (rv.posted desc) + * group_by => STR Group by clause + * + * @return array|int + * @since 1.8.0 + */ +function elgg_get_river(array $options = array()) { + global $CONFIG; + + $defaults = array( + 'ids' => ELGG_ENTITIES_ANY_VALUE, + + 'subject_guids' => ELGG_ENTITIES_ANY_VALUE, + 'object_guids' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_ids' => ELGG_ENTITIES_ANY_VALUE, + 'action_types' => ELGG_ENTITIES_ANY_VALUE, + + 'relationship' => NULL, + 'relationship_guid' => NULL, + 'inverse_relationship' => FALSE, + + 'types' => ELGG_ENTITIES_ANY_VALUE, + 'subtypes' => ELGG_ENTITIES_ANY_VALUE, + 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE, + + 'posted_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'posted_time_upper' => ELGG_ENTITIES_ANY_VALUE, + + 'limit' => 20, + 'offset' => 0, + 'count' => FALSE, + + 'order_by' => 'rv.posted desc', + 'group_by' => ELGG_ENTITIES_ANY_VALUE, + + 'wheres' => array(), + 'joins' => array(), + ); + + $options = array_merge($defaults, $options); + + $singulars = array('id', 'subject_guid', 'object_guid', 'annotation_id', 'action_type', 'type', 'subtype'); + $options = elgg_normalise_plural_options_array($options, $singulars); + + $wheres = $options['wheres']; + + $wheres[] = elgg_get_guid_based_where_sql('rv.id', $options['ids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']); + $wheres[] = elgg_river_get_action_where_sql($options['action_types']); + $wheres[] = elgg_get_river_type_subtype_where_sql('rv', $options['types'], + $options['subtypes'], $options['type_subtype_pairs']); + + if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) { + $wheres[] = "rv.posted >= {$options['posted_time_lower']}"; + } + + if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) { + $wheres[] = "rv.posted <= {$options['posted_time_upper']}"; + } + + $joins = $options['joins']; + + if ($options['relationship_guid']) { + $clauses = elgg_get_entity_relationship_where_sql( + 'rv.subject_guid', + $options['relationship'], + $options['relationship_guid'], + $options['inverse_relationship']); + if ($clauses) { + $wheres = array_merge($wheres, $clauses['wheres']); + $joins = array_merge($joins, $clauses['joins']); + } + } + + // see if any functions failed + // remove empty strings on successful functions + foreach ($wheres as $i => $where) { + if ($where === FALSE) { + return FALSE; + } elseif (empty($where)) { + unset($wheres[$i]); + } + } + + // remove identical where clauses + $wheres = array_unique($wheres); + + if (!$options['count']) { + $query = "SELECT DISTINCT rv.* FROM {$CONFIG->dbprefix}river rv "; + } else { + $query = "SELECT count(DISTINCT rv.id) as total FROM {$CONFIG->dbprefix}river rv "; + } + + // add joins + foreach ($joins as $j) { + $query .= " $j "; + } + + // add wheres + $query .= ' WHERE '; + + foreach ($wheres as $w) { + $query .= " $w AND "; + } + + $query .= elgg_river_get_access_sql(); + + if (!$options['count']) { + $options['group_by'] = sanitise_string($options['group_by']); + if ($options['group_by']) { + $query .= " GROUP BY {$options['group_by']}"; + } + + $options['order_by'] = sanitise_string($options['order_by']); + $query .= " ORDER BY {$options['order_by']}"; + + if ($options['limit']) { + $limit = sanitise_int($options['limit']); + $offset = sanitise_int($options['offset'], false); + $query .= " LIMIT $offset, $limit"; + } + + $river_items = get_data($query, 'elgg_row_to_elgg_river_item'); + _elgg_prefetch_river_entities($river_items); + + return $river_items; + } else { + $total = get_data_row($query); + return (int)$total->total; + } +} + +/** + * Prefetch entities that will be displayed in the river. + * + * @param ElggRiverItem[] $river_items + * @access private + */ +function _elgg_prefetch_river_entities(array $river_items) { + // prefetch objects and subjects + $guids = array(); + foreach ($river_items as $item) { + if ($item->subject_guid && !_elgg_retrieve_cached_entity($item->subject_guid)) { + $guids[$item->subject_guid] = true; + } + if ($item->object_guid && !_elgg_retrieve_cached_entity($item->object_guid)) { + $guids[$item->object_guid] = true; + } + } + if ($guids) { + // avoid creating oversized query + // @todo how to better handle this? + $guids = array_slice($guids, 0, 300, true); + // return value unneeded, just priming cache + elgg_get_entities(array( + 'guids' => array_keys($guids), + 'limit' => 0, + )); + } + + // prefetch object containers + $guids = array(); + foreach ($river_items as $item) { + $object = $item->getObjectEntity(); + if ($object->container_guid && !_elgg_retrieve_cached_entity($object->container_guid)) { + $guids[$object->container_guid] = true; + } + } + if ($guids) { + $guids = array_slice($guids, 0, 300, true); + elgg_get_entities(array( + 'guids' => array_keys($guids), + 'limit' => 0, + )); + } +} + +/** + * List river items + * + * @param array $options Any options from elgg_get_river() plus: + * pagination => BOOL Display pagination links (true) + * + * @return string + * @since 1.8.0 + */ +function elgg_list_river(array $options = array()) { + global $autofeed; + $autofeed = true; + + $defaults = array( + 'offset' => (int) max(get_input('offset', 0), 0), + 'limit' => (int) max(get_input('limit', 20), 0), + 'pagination' => TRUE, + 'list_class' => 'elgg-list-river elgg-river', // @todo remove elgg-river in Elgg 1.9 + ); + + $options = array_merge($defaults, $options); + + if (!$options["limit"] && !$options["offset"]) {
+ // no need for pagination if listing is unlimited
+ $options["pagination"] = false;
+ } + + $options['count'] = TRUE; + $count = elgg_get_river($options); + + $options['count'] = FALSE; + $items = elgg_get_river($options); + + $options['count'] = $count; + $options['items'] = $items; + + return elgg_view('page/components/list', $options); +} + +/** + * Convert a database row to a new ElggRiverItem + * + * @param stdClass $row Database row from the river table + * + * @return ElggRiverItem + * @since 1.8.0 + * @access private + */ +function elgg_row_to_elgg_river_item($row) { + if (!($row instanceof stdClass)) { + return NULL; + } + + return new ElggRiverItem($row); +} + +/** + * Get the river's access where clause + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_river_get_access_sql() { + // rewrite default access where clause to work with river table + return str_replace("and enabled='yes'", '', + str_replace('owner_guid', 'rv.subject_guid', + str_replace('access_id', 'rv.access_id', get_access_sql_suffix()))); +} + +/** + * Returns SQL where clause for type and subtype on river table + * + * @internal This is a simplified version of elgg_get_entity_type_subtype_where_sql() + * which could be used for all queries once the subtypes have been denormalized. + * + * @param string $table 'rv' + * @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 + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_get_river_type_subtype_where_sql($table, $types, $subtypes, $pairs) { + // short circuit if nothing is requested + if (!$types && !$subtypes && !$pairs) { + return ''; + } + + $wheres = array(); + $types_wheres = array(); + $subtypes_wheres = array(); + + // if no pairs, use types and subtypes + if (!is_array($pairs)) { + if ($types) { + if (!is_array($types)) { + $types = array($types); + } + foreach ($types as $type) { + $type = sanitise_string($type); + $types_wheres[] = "({$table}.type = '$type')"; + } + } + + if ($subtypes) { + if (!is_array($subtypes)) { + $subtypes = array($subtypes); + } + foreach ($subtypes as $subtype) { + $subtype = sanitise_string($subtype); + $subtypes_wheres[] = "({$table}.subtype = '$subtype')"; + } + } + + if (is_array($types_wheres) && count($types_wheres)) { + $types_wheres = array(implode(' OR ', $types_wheres)); + } + + if (is_array($subtypes_wheres) && count($subtypes_wheres)) { + $subtypes_wheres = array('(' . implode(' OR ', $subtypes_wheres) . ')'); + } + + $wheres = array(implode(' AND ', array_merge($types_wheres, $subtypes_wheres))); + + } else { + // using type/subtype pairs + foreach ($pairs as $paired_type => $paired_subtypes) { + $paired_type = sanitise_string($paired_type); + if (is_array($paired_subtypes)) { + $paired_subtypes = array_map('sanitise_string', $paired_subtypes); + $paired_subtype_str = implode("','", $paired_subtypes); + if ($paired_subtype_str) { + $wheres[] = "({$table}.type = '$paired_type'" + . " AND {$table}.subtype IN ('$paired_subtype_str'))"; + } + } else { + $paired_subtype = sanitise_string($paired_subtypes); + $wheres[] = "({$table}.type = '$paired_type'" + . " AND {$table}.subtype = '$paired_subtype')"; + } + } + } + + if (is_array($wheres) && count($wheres)) { + $where = implode(' OR ', $wheres); + return "($where)"; + } + + return ''; +} + +/** + * Get the where clause based on river action type strings + * + * @param array $types Array of action type strings + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_river_get_action_where_sql($types) { + if (!$types) { + return ''; + } + + if (!is_array($types)) { + $types = sanitise_string($types); + return "(rv.action_type = '$types')"; + } + + // sanitize types array + $types_sanitized = array(); + foreach ($types as $type) { + $types_sanitized[] = sanitise_string($type); + } + + $type_str = implode("','", $types_sanitized); + return "(rv.action_type IN ('$type_str'))"; +} + +/** + * Get the where clause based on river view strings + * + * @param array $views Array of view strings + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_river_get_view_where_sql($views) { + if (!$views) { + return ''; + } + + if (!is_array($views)) { + $views = sanitise_string($views); + return "(rv.view = '$views')"; + } + + // sanitize views array + $views_sanitized = array(); + foreach ($views as $view) { + $views_sanitized[] = sanitise_string($view); + } + + $view_str = implode("','", $views_sanitized); + return "(rv.view IN ('$view_str'))"; +} + +/** + * Sets the access ID on river items for a particular object + * + * @param int $object_guid The GUID of the entity + * @param int $access_id The access ID + * + * @return bool Depending on success + */ +function update_river_access_by_object($object_guid, $access_id) { + // Sanitise + $object_guid = (int) $object_guid; + $access_id = (int) $access_id; + + // Load config + global $CONFIG; + + // Remove + $query = "update {$CONFIG->dbprefix}river + set access_id = {$access_id} + where object_guid = {$object_guid}"; + return update_data($query); +} + +/** + * Page handler for activity + * + * @param array $page + * @return bool + * @access private + */ +function elgg_river_page_handler($page) { + global $CONFIG; + + elgg_set_page_owner_guid(elgg_get_logged_in_user_guid()); + + // make a URL segment available in page handler script + $page_type = elgg_extract(0, $page, 'all'); + $page_type = preg_replace('[\W]', '', $page_type); + if ($page_type == 'owner') { + $page_type = 'mine'; + } + set_input('page_type', $page_type); + + require_once("{$CONFIG->path}pages/river.php"); + return true; +} + +/** + * Register river unit tests + * @access private + */ +function elgg_river_test($hook, $type, $value) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/river.php'; + return $value; +} + +/** + * Initialize river library + * @access private + */ +function elgg_river_init() { + elgg_register_page_handler('activity', 'elgg_river_page_handler'); + $item = new ElggMenuItem('activity', elgg_echo('activity'), 'activity'); + elgg_register_menu_item('site', $item); + + elgg_register_widget_type('river_widget', elgg_echo('river:widget:title'), elgg_echo('river:widget:description')); + + elgg_register_action('river/delete', '', 'admin'); + + elgg_register_plugin_hook_handler('unit_test', 'system', 'elgg_river_test'); +} + +elgg_register_event_handler('init', 'system', 'elgg_river_init'); diff --git a/engine/lib/river2.php b/engine/lib/river2.php deleted file mode 100644 index 45a7f59ec..000000000 --- a/engine/lib/river2.php +++ /dev/null @@ -1,311 +0,0 @@ -<?php -/** - * Elgg river 2.0. - * Functions for listening for and generating the river separately from the system log. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - -/** - * Adds an item to the river. - * - * @param string $view The view that will handle the river item (must exist) - * @param string $action_type An arbitrary one-word string to define the action (eg 'comment', 'create') - * @param int $subject_guid The GUID of the entity doing the action - * @param int $object_guid The GUID of the entity being acted upon - * @param int $access_id The access ID of the river item (default: same as the object) - * @param int $posted The UNIX epoch timestamp of the river item (default: now) - * @return true|false Depending on success - */ -function add_to_river($view,$action_type,$subject_guid,$object_guid,$access_id = "",$posted = 0, $annotation_id = 0) { - // Sanitise variables - if (!elgg_view_exists($view)) { - return false; - } - if (!($subject = get_entity($subject_guid))) { - return false; - } - if (!($object = get_entity($object_guid))) { - return false; - } - if (empty($action_type)) { - return false; - } - if ($posted == 0) { - $posted = time(); - } - if ($access_id === "") { - $access_id = $object->access_id; - } - $annotation_id = (int)$annotation_id; - $type = $object->getType(); - $subtype = $object->getSubtype(); - $action_type = sanitise_string($action_type); - - // Load config - global $CONFIG; - - // Attempt to save river item; return success status - return insert_data("insert into {$CONFIG->dbprefix}river " . - " set type = '{$type}', " . - " subtype = '{$subtype}', " . - " action_type = '{$action_type}', " . - " access_id = {$access_id}, " . - " view = '{$view}', " . - " subject_guid = {$subject_guid}, " . - " object_guid = {$object_guid}, " . - " annotation_id = {$annotation_id}, " . - " posted = {$posted} "); -} - -/** - * Removes all items relating to a particular acting entity from the river - * - * @param int $subject_guid The GUID of the entity - * @return true|false Depending on success - */ -function remove_from_river_by_subject($subject_guid) { - // Sanitise - $subject_guid = (int) $subject_guid; - - // Load config - global $CONFIG; - - // Remove - return delete_data("delete from {$CONFIG->dbprefix}river where subject_guid = {$subject_guid}"); -} - -/** - * Removes all items relating to a particular entity being acted upon from the river - * - * @param int $object_guid The GUID of the entity - * @return true|false Depending on success - */ -function remove_from_river_by_object($object_guid) { - // Sanitise - $object_guid = (int) $object_guid; - - // Load config - global $CONFIG; - - // Remove - return delete_data("delete from {$CONFIG->dbprefix}river where object_guid = {$object_guid}"); -} - -/** - * Removes all items relating to a particular annotation being acted upon from the river - * - * @param int annotation_id The ID of the annotation - * @return true|false Depending on success - */ -function remove_from_river_by_annotation($annotation_id) { - // Sanitise - $annotation_id = (int) $annotation_id; - - // Load config - global $CONFIG; - - // Remove - return delete_data("delete from {$CONFIG->dbprefix}river where annotation_id = {$annotation_id}"); -} - -/** - * Sets the access ID on river items for a particular object - * - * @param int $object_guid The GUID of the entity - * @param int $access_id The access ID - * @return true|false Depending on success - */ -function update_river_access_by_object($object_guid, $access_id) { - // Sanitise - $object_guid = (int) $object_guid; - $access_id = (int) $access_id; - - // Load config - global $CONFIG; - - // Remove - return update_data("update {$CONFIG->dbprefix}river set access_id = {$access_id} where object_guid = {$object_guid}"); -} - -/** - * Retrieves items from the river. All parameters are optional. - * - * @param int|array $subject_guid Acting entity to restrict to. Default: all - * @param int|array $object_guid Entity being acted on to restrict to. Default: all - * @param string $subject_relationship If set to a relationship type, this will use - * $subject_guid as the starting point and set the subjects to be all users this - * entity has this relationship with (eg 'friend'). Default: blank - * @param string $type The type of entity to restrict to. Default: all - * @param string $subtype The subtype of entity to restrict to. Default: all - * @param string $action_type The type of river action to restrict to. Default: all - * @param int $limit The number of items to retrieve. Default: 20 - * @param int $offset The page offset. Default: 0 - * @param int $posted_min The minimum time period to look at. Default: none - * @param int $posted_max The maximum time period to look at. Default: none - * @return array|false Depending on success - */ -function get_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', $type = '', - $subtype = '', $action_type = '', $limit = 20, $offset = 0, $posted_min = 0, $posted_max = 0) { - - // Get config - global $CONFIG; - - // Sanitise variables - if (!is_array($subject_guid)) { - $subject_guid = (int) $subject_guid; - } else { - foreach($subject_guid as $key => $temp) { - $subject_guid[$key] = (int) $temp; - } - } - if (!is_array($object_guid)) { - $object_guid = (int) $object_guid; - } else { - foreach($object_guid as $key => $temp) { - $object_guid[$key] = (int) $temp; - } - } - if (!empty($type)) { - $type = sanitise_string($type); - } - if (!empty($subtype)) { - $subtype = sanitise_string($subtype); - } - if (!empty($action_type)) { - $action_type = sanitise_string($action_type); - } - $limit = (int) $limit; - $offset = (int) $offset; - $posted_min = (int) $posted_min; - $posted_max = (int) $posted_max; - - // Construct 'where' clauses for the river - $where = array(); - $where[] = str_replace("and enabled='yes'",'',str_replace('owner_guid','subject_guid',get_access_sql_suffix())); - - if (empty($subject_relationship)) { - if (!empty($subject_guid)) { - if (!is_array($subject_guid)) { - $where[] = " subject_guid = {$subject_guid} "; - } else { - $where[] = " subject_guid in (" . implode(',',$subject_guid) . ") "; - } - } - } else { - if (!is_array($subject_guid)) { - if ($entities = elgg_get_entities_from_relationship(array( - 'relationship' => $subject_relationship, - 'relationship_guid' => $subject_guid, - 'limit' => 9999)) - ) { - $guids = array(); - foreach($entities as $entity) { - $guids[] = (int) $entity->guid; - } - // $guids[] = $subject_guid; - $where[] = " subject_guid in (" . implode(',',$guids) . ") "; - } else { - return array(); - } - } - } - if (!empty($object_guid)) - if (!is_array($object_guid)) { - $where[] = " object_guid = {$object_guid} "; - } else { - $where[] = " object_guid in (" . implode(',',$object_guid) . ") "; - } - if (!empty($type)) { - $where[] = " type = '{$type}' "; - } - if (!empty($subtype)) { - $where[] = " subtype = '{$subtype}' "; - } - if (!empty($action_type)) { - $where[] = " action_type = '{$action_type}' "; - } - if (!empty($posted_min)) { - $where[] = " posted > {$posted_min} "; - } - if (!empty($posted_max)) { - $where[] = " posted < {$posted_max} "; - } - - $whereclause = implode(' and ', $where); - - // Construct main SQL - $sql = "select id,type,subtype,action_type,access_id,view,subject_guid,object_guid,annotation_id,posted" . - " from {$CONFIG->dbprefix}river where {$whereclause} order by posted desc limit {$offset},{$limit}"; - - // Get data - return get_data($sql); -} - -/** - * Returns a human-readable representation of a river item - * - * @see get_river_items - * - * @param stdClass $item A river item object as returned from get_river_items - * @return string|false Depending on success - */ -function elgg_view_river_item($item) { - if (isset($item->view)) { - $object = get_entity($item->object_guid); - if (!$object) { - $body = elgg_view('river/item/noaccess'); - } else { - if (elgg_view_exists($item->view)) { - $body = elgg_view($item->view,array( - 'item' => $item - )); - } - } - return elgg_view('river/item/wrapper',array( - 'item' => $item, - 'body' => $body - )); - } - return false; -} - -/** - * Returns a human-readable version of the river. - * - * @param int|array $subject_guid Acting entity to restrict to. Default: all - * @param int|array $object_guid Entity being acted on to restrict to. Default: all - * @param string $subject_relationship If set to a relationship type, this will use - * $subject_guid as the starting point and set the subjects to be all users this - * entity has this relationship with (eg 'friend'). Default: blank - * @param string $type The type of entity to restrict to. Default: all - * @param string $subtype The subtype of entity to restrict to. Default: all - * @param string $action_type The type of river action to restrict to. Default: all - * @param int $limit The number of items to retrieve. Default: 20 - * @param int $posted_min The minimum time period to look at. Default: none - * @param int $posted_max The maximum time period to look at. Default: none - * @return string Human-readable river. - */ -function elgg_view_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', - $type = '', $subtype = '', $action_type = '', $limit = 20, $posted_min = 0, $posted_max = 0, $pagination = true) { - - // Get input from outside world and sanitise it - $offset = (int) get_input('offset',0); - - // Get river items, if they exist - if ($riveritems = get_river_items($subject_guid,$object_guid,$subject_relationship,$type,$subtype,$action_type,($limit + 1),$offset,$posted_min,$posted_max)) { - - return elgg_view('river/item/list',array( - 'limit' => $limit, - 'offset' => $offset, - 'items' => $riveritems, - 'pagination' => $pagination - )); - - } - - return ''; -} diff --git a/engine/lib/sessions.php b/engine/lib/sessions.php index fdc6d1806..e3d5ce9cd 100644 --- a/engine/lib/sessions.php +++ b/engine/lib/sessions.php @@ -4,137 +4,43 @@ * Elgg session management * Functions to manage logins * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Session */ /** Elgg magic session */ global $SESSION; /** - * Magic session class. - * This class is intended to extend the $_SESSION magic variable by providing an API hook - * to plug in other values. + * Return the current logged in user, or NULL if no user is logged in. * - * Primarily this is intended to provide a way of supplying "logged in user" details without touching the session - * (which can cause problems when accessed server side). + * If no user can be found in the current session, a plugin + * hook - 'session:get' 'user' to give plugin authors another + * way to provide user details to the ACL system without touching the session. * - * If a value is present in the session then that value is returned, otherwise a plugin hook 'session:get', '$var' is called, - * where $var is the variable being requested. - * - * Setting values will store variables in the session in the normal way. - * - * LIMITATIONS: You can not access multidimensional arrays - * - * This is EXPERIMENTAL. + * @return ElggUser */ -class ElggSession implements ArrayAccess { - /** Local cache of trigger retrieved variables */ - private static $__localcache; - - function __isset($key) { - return $this->offsetExists($key); - } - - /** Set a value, go straight to session. */ - function offsetSet($key, $value) { - $_SESSION[$key] = $value; - } - - /** - * Get a variable from either the session, or if its not in the session attempt to get it from - * an api call. - */ - function offsetGet($key) { - if (!ElggSession::$__localcache) { - ElggSession::$__localcache = array(); - } - - if (isset($_SESSION[$key])) { - return $_SESSION[$key]; - } - - if (isset(ElggSession::$__localcache[$key])) { - return ElggSession::$__localcache[$key]; - } - - $value = null; - $value = trigger_plugin_hook('session:get', $key, null, $value); - - ElggSession::$__localcache[$key] = $value; - - return ElggSession::$__localcache[$key]; - } - - /** - * Unset a value from the cache and the session. - */ - function offsetUnset($key) { - unset(ElggSession::$__localcache[$key]); - unset($_SESSION[$key]); - } - - /** - * Return whether the value is set in either the session or the cache. - */ - function offsetExists($offset) { - if (isset(ElggSession::$__localcache[$offset])) { - return true; - } - - if (isset($_SESSION[$offset])) { - return true; - } - - if ($this->offsetGet($offset)){ - return true; - } - } - - - // Alias functions - function get($key) { - return $this->offsetGet($key); - } - - function set($key, $value) { - return $this->offsetSet($key, $value); - } - - function del($key) { - return $this->offsetUnset($key); - } -} - - -/** - * Return the current logged in user, or null if no user is logged in. - * - * If no user can be found in the current session, a plugin hook - 'session:get' 'user' to give plugin - * authors another way to provide user details to the ACL system without touching the session. - */ -function get_loggedin_user() { +function elgg_get_logged_in_user_entity() { global $SESSION; if (isset($SESSION)) { return $SESSION['user']; } - return false; + return NULL; } /** * Return the current logged in user by id. * - * @see get_loggedin_user() + * @see elgg_get_logged_in_user_entity() * @return int */ -function get_loggedin_userid() { - $user = get_loggedin_user(); - if ($user) +function elgg_get_logged_in_user_guid() { + $user = elgg_get_logged_in_user_entity(); + if ($user) { return $user->guid; + } return 0; } @@ -142,14 +48,10 @@ function get_loggedin_userid() { /** * Returns whether or not the user is currently logged in * - * @return true|false + * @return bool */ -function isloggedin() { - if (!is_installed()) { - return false; - } - - $user = get_loggedin_user(); +function elgg_is_logged_in() { + $user = elgg_get_logged_in_user_entity(); if ((isset($user)) && ($user instanceof ElggUser) && ($user->guid > 0)) { return true; @@ -161,126 +63,136 @@ function isloggedin() { /** * Returns whether or not the user is currently logged in and that they are an admin user. * - * @uses isloggedin() - * @return true|false + * @return bool */ -function isadminloggedin() { - if (!is_installed()) { - return false; - } +function elgg_is_admin_logged_in() { + $user = elgg_get_logged_in_user_entity(); - $user = get_loggedin_user(); - - if ((isloggedin()) && (($user->admin || $user->siteadmin))) { - return true; + if ((elgg_is_logged_in()) && $user->isAdmin()) { + return TRUE; } - return false; + return FALSE; } /** * Check if the given user has full access. + * * @todo: Will always return full access if the user is an admin. * - * @param $user_guid + * @param int $user_guid The user to check + * * @return bool + * @since 1.7.1 */ function elgg_is_admin_user($user_guid) { global $CONFIG; - // cannot use metadata here because of recursion - - // caching is done at the db level so no need to here. - $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as e, - {$CONFIG->dbprefix}metastrings as ms1, - {$CONFIG->dbprefix}metastrings as ms2, - {$CONFIG->dbprefix}metadata as md - WHERE ( - ( - (ms1.string = 'admin' AND ms2.string = 'yes') - OR (ms1.string = 'admin' AND ms2.string = '1') - ) - AND md.name_id = ms1.id AND md.value_id = ms2.id - AND e.guid = md.entity_guid - AND e.guid = {$user_guid} - AND e.banned = 'no' - )"; -// OR ( -// ms1.string = 'admin' AND ms2.string = '1' -// AND md.name_id = ms1.id AND md.value_id = ms2.id -// AND e.guid = md.entity_guid -// AND e.guid = {$user_guid} -// AND e.banned = 'no' -// )"; + $user_guid = (int)$user_guid; + // cannot use magic metadata here because of recursion + + // must support the old way of getting admin from metadata + // in order to run the upgrade to move it into the users table. + $version = (int) datalist_get('version'); + + if ($version < 2010040201) { + $admin = get_metastring_id('admin'); + $yes = get_metastring_id('yes'); + $one = get_metastring_id('1'); + + $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as e, + {$CONFIG->dbprefix}metadata as md + WHERE ( + md.name_id = '$admin' + AND md.value_id IN ('$yes', '$one') + AND e.guid = md.entity_guid + AND e.guid = {$user_guid} + AND e.banned = 'no' + )"; + } else { + $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as e + WHERE ( + e.guid = {$user_guid} + AND e.admin = 'yes' + )"; + } // normalizing the results from get_data() // See #1242 $info = get_data($query); - if (!((is_array($info) && count($info) < 1) || $info === false)) { - return true; + if (!((is_array($info) && count($info) < 1) || $info === FALSE)) { + return TRUE; } - return false; + return FALSE; } /** - * Perform standard authentication with a given username and password. - * Returns an ElggUser object for use with login. + * Perform user authentication with a given username and password. + * + * @warning This returns an error message on failure. Use the identical operator to check + * for access: if (true === elgg_authenticate()) { ... }. + * * * @see login - * @param string $username The username, optionally (for standard logins) - * @param string $password The password, optionally (for standard logins) - * @return ElggUser|false The authenticated user object, or false on failure. + * + * @param string $username The username + * @param string $password The password + * + * @return true|string True or an error message on failure + * @access private */ - -function authenticate($username, $password) { - if (pam_authenticate(array('username' => $username, 'password' => $password))) { - return get_user_by_username($username); +function elgg_authenticate($username, $password) { + $pam = new ElggPAM('user'); + $credentials = array('username' => $username, 'password' => $password); + $result = $pam->authenticate($credentials); + if (!$result) { + return $pam->getFailureMessage(); } - - return false; + return true; } /** * Hook into the PAM system which accepts a username and password and attempts to authenticate * it against a known user. * - * @param array $credentials Associated array of credentials passed to pam_authenticate. This function expects - * 'username' and 'password' (cleartext). + * @param array $credentials Associated array of credentials passed to + * Elgg's PAM system. This function expects + * 'username' and 'password' (cleartext). + * + * @return bool + * @throws LoginException + * @access private */ -function pam_auth_userpass($credentials = NULL) { - - if (is_array($credentials) && ($credentials['username']) && ($credentials['password'])) { - if ($user = get_user_by_username($credentials['username'])) { +function pam_auth_userpass(array $credentials = array()) { - // Let admins log in without validating their email, but normal users must have validated their email or been admin created - if ((!$user->admin) && (!$user->validated) && (!$user->admin_created)) { - return false; - } + if (!isset($credentials['username']) || !isset($credentials['password'])) { + return false; + } - // User has been banned, so prevent from logging in - if ($user->isBanned()) { - return false; - } + $user = get_user_by_username($credentials['username']); + if (!$user) { + throw new LoginException(elgg_echo('LoginException:UsernameFailure')); + } - if ($user->password == generate_user_password($user, $credentials['password'])) { - return true; - } else { - // Password failed, log. - log_login_failure($user->guid); - } + if (check_rate_limit_exceeded($user->guid)) { + throw new LoginException(elgg_echo('LoginException:AccountLocked')); + } - } + if ($user->password !== generate_user_password($user, $credentials['password'])) { + log_login_failure($user->guid); + throw new LoginException(elgg_echo('LoginException:PasswordFailure')); } - return false; + return true; } /** * Log a failed login for $user_guid * - * @param $user_guid - * @return bool on success + * @param int $user_guid User GUID + * + * @return bool */ function log_login_failure($user_guid) { $user_guid = (int)$user_guid; @@ -301,8 +213,9 @@ function log_login_failure($user_guid) { /** * Resets the fail login count for $user_guid * - * @param $user_guid - * @return bool on success (success = user has no logged failed attempts) + * @param int $user_guid User GUID + * + * @return bool true on success (success = user has no logged failed attempts) */ function reset_login_failure_count($user_guid) { $user_guid = (int)$user_guid; @@ -312,7 +225,7 @@ function reset_login_failure_count($user_guid) { $fails = (int)$user->getPrivateSetting("login_failures"); if ($fails) { - for ($n=1; $n <= $fails; $n++) { + for ($n = 1; $n <= $fails; $n++) { $user->removePrivateSetting("login_failure_$n"); } @@ -331,11 +244,12 @@ function reset_login_failure_count($user_guid) { /** * Checks if the rate limit of failed logins has been exceeded for $user_guid. * - * @param $user_guid + * @param int $user_guid User GUID + * * @return bool on exceeded limit. */ function check_rate_limit_exceeded($user_guid) { - // 5 failures in 5 minutes causes temporary block on logins + // 5 failures in 5 minutes causes temporary block on logins $limit = 5; $user_guid = (int)$user_guid; $user = get_entity($user_guid); @@ -345,13 +259,13 @@ function check_rate_limit_exceeded($user_guid) { if ($fails >= $limit) { $cnt = 0; $time = time(); - for ($n=$fails; $n>0; $n--) { + for ($n = $fails; $n > 0; $n--) { $f = $user->getPrivateSetting("login_failure_$n"); - if ($f > $time - (60*5)) { + if ($f > $time - (60 * 5)) { $cnt++; } - if ($cnt==$limit) { + if ($cnt == $limit) { // Limit reached return true; } @@ -364,24 +278,20 @@ function check_rate_limit_exceeded($user_guid) { /** * Logs in a specified ElggUser. For standard registration, use in conjunction - * with authenticate. + * with elgg_authenticate. + * + * @see elgg_authenticate + * + * @param ElggUser $user A valid Elgg user object + * @param boolean $persistent Should this be a persistent login? * - * @see authenticate - * @param ElggUser $user A valid Elgg user object - * @param boolean $persistent Should this be a persistent login? - * @return true|false Whether login was successful + * @return true or throws exception + * @throws LoginException */ function login(ElggUser $user, $persistent = false) { - global $CONFIG; - // User is banned, return false. if ($user->isBanned()) { - return false; - } - - // Check rate limit - if (check_rate_limit_exceeded($user->guid)) { - return false; + throw new LoginException(elgg_echo('LoginException:BannedUser')); } $_SESSION['user'] = $user; @@ -395,18 +305,18 @@ function login(ElggUser $user, $persistent = false) { $code = (md5($user->name . $user->username . time() . rand())); $_SESSION['code'] = $code; $user->code = md5($code); - setcookie("elggperm", $code, (time()+(86400 * 30)),"/"); + setcookie("elggperm", $code, (time() + (86400 * 30)), "/"); } - if (!$user->save() || !trigger_elgg_event('login','user',$user)) { + if (!$user->save() || !elgg_trigger_event('login', 'user', $user)) { unset($_SESSION['username']); unset($_SESSION['name']); unset($_SESSION['code']); unset($_SESSION['guid']); unset($_SESSION['id']); unset($_SESSION['user']); - setcookie("elggperm", "", (time()-(86400 * 30)),"/"); - return false; + setcookie("elggperm", "", (time() - (86400 * 30)), "/"); + throw new LoginException(elgg_echo('LoginException:Unknown')); } // Users privilege has been elevated, so change the session id (prevents session fixation) @@ -416,26 +326,23 @@ function login(ElggUser $user, $persistent = false) { set_last_login($_SESSION['guid']); reset_login_failure_count($user->guid); // Reset any previous failed login attempts - // Set admin shortcut flag if this is an admin -// if (isadminloggedin()) { -// //@todo REMOVE THIS. -// global $is_admin; -// $is_admin = true; -// } - + // if memcache is enabled, invalidate the user in memcache @see https://github.com/Elgg/Elgg/issues/3143 + if (is_memcache_available()) { + // this needs to happen with a shutdown function because of the timing with set_last_login() + register_shutdown_function("_elgg_invalidate_memcache_for_entity", $_SESSION['guid']); + } + return true; } /** * Log the current user out * - * @return true|false + * @return bool */ function logout() { - global $CONFIG; - - if (isset($_SESSION['user'])) { - if (!trigger_elgg_event('logout','user',$_SESSION['user'])) { + if (isset($_SESSION['user'])) { + if (!elgg_trigger_event('logout', 'user', $_SESSION['user'])) { return false; } $_SESSION['user']->code = ""; @@ -449,7 +356,7 @@ function logout() { unset($_SESSION['id']); unset($_SESSION['user']); - setcookie("elggperm", "", (time()-(86400 * 30)),"/"); + setcookie("elggperm", "", (time() - (86400 * 30)), "/"); // pass along any messages $old_msg = $_SESSION['msg']; @@ -457,71 +364,47 @@ function logout() { session_destroy(); // starting a default session to store any post-logout messages. - session_init(NULL, NULL, NULL); + _elgg_session_boot(NULL, NULL, NULL); $_SESSION['msg'] = $old_msg; return TRUE; } /** - * Returns a fingerprint for an elgg session. - * - * @return string - */ -function get_session_fingerprint() { - global $CONFIG; - - return md5($_SERVER['HTTP_USER_AGENT'] . get_site_secret()); -} - -/** * Initialises the system session and potentially logs the user in * * This function looks for: * * 1. $_SESSION['id'] - if not present, we're logged out, and this is set to 0 - * 2. The cookie 'elggperm' - if present, checks it for an authentication token, validates it, and potentially logs the user in + * 2. The cookie 'elggperm' - if present, checks it for an authentication + * token, validates it, and potentially logs the user in * * @uses $_SESSION - * @param unknown_type $event - * @param unknown_type $object_type - * @param unknown_type $object + * + * @return bool + * @access private */ -function session_init($event, $object_type, $object) { +function _elgg_session_boot() { global $DB_PREFIX, $CONFIG; - if (!is_db_installed()) { - return false; - } - // Use database for sessions // HACK to allow access to prefix after object destruction $DB_PREFIX = $CONFIG->dbprefix; if ((!isset($CONFIG->use_file_sessions))) { - session_set_save_handler("__elgg_session_open", - "__elgg_session_close", - "__elgg_session_read", - "__elgg_session_write", - "__elgg_session_destroy", - "__elgg_session_gc"); + session_set_save_handler("_elgg_session_open", + "_elgg_session_close", + "_elgg_session_read", + "_elgg_session_write", + "_elgg_session_destroy", + "_elgg_session_gc"); } session_name('Elgg'); session_start(); - // Do some sanity checking by generating a fingerprint (makes some XSS attacks harder) - if (isset($_SESSION['__elgg_fingerprint'])) { - if ($_SESSION['__elgg_fingerprint'] != get_session_fingerprint()) { - session_destroy(); - return false; - } - } else { - $_SESSION['__elgg_fingerprint'] = get_session_fingerprint(); - } - // Generate a simple token (private from potentially public session id) if (!isset($_SESSION['__elgg_session'])) { - $_SESSION['__elgg_session'] = md5(microtime().rand()); + $_SESSION['__elgg_session'] = md5(microtime() . rand()); } // test whether we have a user session @@ -532,7 +415,7 @@ function session_init($event, $object_type, $object) { unset($_SESSION['id']); unset($_SESSION['guid']); unset($_SESSION['code']); - + // is there a remember me cookie if (isset($_COOKIE['elggperm'])) { // we have a cookie, so try to log the user in @@ -545,7 +428,7 @@ function session_init($event, $object_type, $object) { $_SESSION['guid'] = $_SESSION['id']; $_SESSION['code'] = $_COOKIE['elggperm']; } - } + } } else { // we have a session and we have already checked the fingerprint // reload the user object from database in case it has changed during the session @@ -566,8 +449,8 @@ function session_init($event, $object_type, $object) { set_last_action($_SESSION['guid']); } - register_action("login",true); - register_action("logout"); + elgg_register_action('login', '', 'public'); + elgg_register_action('logout'); // Register a default PAM handler register_pam_handler('pam_auth_userpass'); @@ -582,42 +465,48 @@ function session_init($event, $object_type, $object) { return false; } - // Since we have loaded a new user, this user may have different language preferences - register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); - return true; } /** * Used at the top of a page to mark it as logged in users only. * + * @return void */ function gatekeeper() { - if (!isloggedin()) { + if (!elgg_is_logged_in()) { $_SESSION['last_forward_from'] = current_page_url(); register_error(elgg_echo('loggedinrequired')); - forward(); + forward('', 'login'); } } /** * Used at the top of a page to mark it as logged in admin or siteadmin only. * + * @return void */ function admin_gatekeeper() { gatekeeper(); - if (!isadminloggedin()) { + if (!elgg_is_admin_logged_in()) { $_SESSION['last_forward_from'] = current_page_url(); register_error(elgg_echo('adminrequired')); - forward(); + forward('', 'admin'); } } /** - * DB Based session handling code. + * Handles opening a session in the DB + * + * @param string $save_path The path to save the sessions + * @param string $session_name The name of the session + * + * @return true + * @todo Document + * @access private */ -function __elgg_session_open($save_path, $session_name) { +function _elgg_session_open($save_path, $session_name) { global $sess_save_path; $sess_save_path = $save_path; @@ -625,16 +514,27 @@ function __elgg_session_open($save_path, $session_name) { } /** - * DB Based session handling code. + * Closes a session + * + * @todo implement + * @todo document + * + * @return true + * @access private */ -function __elgg_session_close() { +function _elgg_session_close() { return true; } /** - * DB Based session handling code. + * Read the session data from DB failing back to file. + * + * @param string $id The session ID + * + * @return string + * @access private */ -function __elgg_session_read($id) { +function _elgg_session_read($id) { global $DB_PREFIX; $id = sanitise_string($id); @@ -660,9 +560,15 @@ function __elgg_session_read($id) { } /** - * DB Based session handling code. + * Write session data to the DB falling back to file. + * + * @param string $id The session ID + * @param mixed $sess_data Session data + * + * @return bool + * @access private */ -function __elgg_session_write($id, $sess_data) { +function _elgg_session_write($id, $sess_data) { global $DB_PREFIX; $id = sanitise_string($id); @@ -675,7 +581,7 @@ function __elgg_session_write($id, $sess_data) { (session, ts, data) VALUES ('$id', '$time', '$sess_data_sanitised')"; - if (insert_data($q)!==false) { + if (insert_data($q) !== false) { return true; } } catch (DatabaseException $e) { @@ -695,9 +601,14 @@ function __elgg_session_write($id, $sess_data) { } /** - * DB Based session handling code. + * Destroy a DB session, falling back to file. + * + * @param string $id Session ID + * + * @return bool + * @access private */ -function __elgg_session_destroy($id) { +function _elgg_session_destroy($id) { global $DB_PREFIX; $id = sanitise_string($id); @@ -710,24 +621,28 @@ function __elgg_session_destroy($id) { global $sess_save_path; $sess_file = "$sess_save_path/sess_$id"; - return(@unlink($sess_file)); + return @unlink($sess_file); } - - return false; } /** - * DB Based session handling code. + * Perform garbage collection on session table / files + * + * @param int $maxlifetime Max age of a session + * + * @return bool + * @access private */ -function __elgg_session_gc($maxlifetime) { +function _elgg_session_gc($maxlifetime) { global $DB_PREFIX; - $life = time()-$maxlifetime; + $life = time() - $maxlifetime; try { return (bool)delete_data("DELETE from {$DB_PREFIX}users_sessions where ts<'$life'"); } catch (DatabaseException $e) { - // Fall back to file store in this case, since this likely means that the database hasn't been upgraded + // Fall back to file store in this case, since this likely means that the database + // hasn't been upgraded global $sess_save_path; foreach (glob("$sess_save_path/sess_*") as $filename) { @@ -739,5 +654,3 @@ function __elgg_session_gc($maxlifetime) { return true; } - -register_elgg_event_handler("boot","system","session_init",20); diff --git a/engine/lib/sites.php b/engine/lib/sites.php index 13521a257..3de0eccc2 100644 --- a/engine/lib/sites.php +++ b/engine/lib/sites.php @@ -3,252 +3,43 @@ * Elgg sites * Functions to manage multiple or single sites in an Elgg install * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage DataModel.Site */ /** - * ElggSite - * Representation of a "site" in the system. - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Core + * Get an ElggSite entity (default is current site) + * + * @param int $site_guid Optional. Site GUID. + * + * @return ElggSite + * @since 1.8.0 */ -class ElggSite extends ElggEntity { - /** - * Initialise the attributes array. - * This is vital to distinguish between metadata and base parameters. - * - * Place your base parameters here. - */ - protected function initialise_attributes() { - parent::initialise_attributes(); - - $this->attributes['type'] = "site"; - $this->attributes['name'] = ""; - $this->attributes['description'] = ""; - $this->attributes['url'] = ""; - $this->attributes['tables_split'] = 2; - } - - /** - * Construct a new site object, optionally from a given id value. - * - * @param mixed $guid If an int, load that GUID. - * If a db row then will attempt to load the rest of the data. - * @throws Exception if there was a problem creating the site. - */ - function __construct($guid = null) { - $this->initialise_attributes(); - - if (!empty($guid)) { - // Is $guid is a DB row - either a entity row, or a site table row. - if ($guid instanceof stdClass) { - // Load the rest - if (!$this->load($guid->guid)) { - throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid)); - } - } - - // Is $guid is an ElggSite? Use a copy constructor - else if ($guid instanceof ElggSite) { - elgg_deprecated_notice('This type of usage of the ElggSite constructor was deprecated. Please use the clone method.', 1.7); - - foreach ($guid->attributes as $key => $value) { - $this->attributes[$key] = $value; - } - } - - // Is this is an ElggEntity but not an ElggSite = ERROR! - else if ($guid instanceof ElggEntity) { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggSite')); - } - - // See if this is a URL - else if (strpos($guid, "http") !== false) { - $guid = get_site_by_url($guid); - foreach ($guid->attributes as $key => $value) { - $this->attributes[$key] = $value; - } - } - - // We assume if we have got this far, $guid is an int - else if (is_numeric($guid)) { - if (!$this->load($guid)) { - throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid)); - } - } - - else { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); - } - } - } - - /** - * Override the load function. - * This function will ensure that all data is loaded (were possible), so - * if only part of the ElggSite is loaded, it'll load the rest. - * - * @param int $guid - */ - protected function load($guid) { - // Test to see if we have the generic stuff - if (!parent::load($guid)) { - return false; - } - - // Check the type - if ($this->attributes['type']!='site') { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class())); - } - - // Load missing data - $row = get_site_entity_as_row($guid); - if (($row) && (!$this->isFullyLoaded())) { - // If $row isn't a cached copy then increment the counter - $this->attributes['tables_loaded'] ++; - } - - // Now put these into the attributes array as core values - $objarray = (array) $row; - foreach($objarray as $key => $value) { - $this->attributes[$key] = $value; - } - - return true; - } - - /** - * Override the save function. - */ - public function save() { - // Save generic stuff - if (!parent::save()) { - return false; - } - - // Now save specific stuff - return create_site_entity($this->get('guid'), $this->get('name'), $this->get('description'), $this->get('url')); - } - - /** - * Delete this site. - */ - public function delete() { - global $CONFIG; - if ($CONFIG->site->getGUID() == $this->guid) { - throw new SecurityException('SecurityException:deletedisablecurrentsite'); - } - - return parent::delete(); - } - - /** - * Disable override to add safety rail. - * - * @param unknown_type $reason - */ - public function disable($reason = "") { - global $CONFIG; - - if ($CONFIG->site->getGUID() == $this->guid) { - throw new SecurityException('SecurityException:deletedisablecurrentsite'); - } - - return parent::disable($reason); - } - - /** - * Return a list of users using this site. - * - * @param int $limit - * @param int $offset - * @return array of ElggUsers - */ - public function getMembers($limit = 10, $offset = 0) { - get_site_members($this->getGUID(), $limit, $offset); - } - - /** - * Add a user to the site. - * - * @param int $user_guid - */ - public function addUser($user_guid) { - return add_site_user($this->getGUID(), $user_guid); - } - - /** - * Remove a site user. - * - * @param int $user_guid - */ - public function removeUser($user_guid) { - return remove_site_user($this->getGUID(), $user_guid); - } - - /** - * Get an array of member ElggObjects. - * - * @param string $subtype - * @param int $limit - * @param int $offset - */ - public function getObjects($subtype="", $limit = 10, $offset = 0) { - get_site_objects($this->getGUID(), $subtype, $limit, $offset); - } - - /** - * Add an object to the site. - * - * @param int $user_id - */ - public function addObject($object_guid) { - return add_site_object($this->getGUID(), $object_guid); - } +function elgg_get_site_entity($site_guid = 0) { + global $CONFIG; - /** - * Remove a site user. - * - * @param int $user_id - */ - public function removeObject($object_guid) { - return remove_site_object($this->getGUID(), $object_guid); + $result = false; + + if ($site_guid == 0) { + $site = $CONFIG->site; + } else { + $site = get_entity($site_guid); } - - /** - * Get the collections associated with a site. - * - * @param string $type - * @param int $limit - * @param int $offset - * @return unknown - */ - public function getCollections($subtype="", $limit = 10, $offset = 0) { - get_site_collections($this->getGUID(), $subtype, $limit, $offset); + + if ($site instanceof ElggSite) { + $result = $site; } - // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// - - /** - * Return an array of fields which can be exported. - */ - public function getExportableValues() { - return array_merge(parent::getExportableValues(), array( - 'name', - 'description', - 'url', - )); - } + return $result; } /** * Return the site specific details of a site by a row. * - * @param int $guid + * @param int $guid The site GUID + * + * @return mixed + * @access private */ function get_site_entity_as_row($guid) { global $CONFIG; @@ -258,13 +49,16 @@ function get_site_entity_as_row($guid) { } /** - * Create or update the extras table for a given site. + * Create or update the entities table for a given site. * Call create_entity first. * - * @param int $guid - * @param string $name - * @param string $description - * @param string $url + * @param int $guid Site GUID + * @param string $name Site name + * @param string $description Site Description + * @param string $url URL of the site + * + * @return bool + * @access private */ function create_site_entity($guid, $name, $description, $url) { global $CONFIG; @@ -278,12 +72,16 @@ function create_site_entity($guid, $name, $description, $url) { if ($row) { // Exists and you have access to it - if ($exists = get_data_row("SELECT guid from {$CONFIG->dbprefix}sites_entity where guid = {$guid}")) { - $result = update_data("UPDATE {$CONFIG->dbprefix}sites_entity set name='$name', description='$description', url='$url' where guid=$guid"); - if ($result!=false) { + $query = "SELECT guid from {$CONFIG->dbprefix}sites_entity where guid = {$guid}"; + if ($exists = get_data_row($query)) { + $query = "UPDATE {$CONFIG->dbprefix}sites_entity + set name='$name', description='$description', url='$url' where guid=$guid"; + $result = update_data($query); + + if ($result != false) { // Update succeeded, continue $entity = get_entity($guid); - if (trigger_elgg_event('update',$entity->type,$entity)) { + if (elgg_trigger_event('update', $entity->type, $entity)) { return $guid; } else { $entity->delete(); @@ -292,10 +90,13 @@ function create_site_entity($guid, $name, $description, $url) { } } else { // Update failed, attempt an insert. - $result = insert_data("INSERT into {$CONFIG->dbprefix}sites_entity (guid, name, description, url) values ($guid, '$name','$description','$url')"); - if ($result!==false) { + $query = "INSERT into {$CONFIG->dbprefix}sites_entity + (guid, name, description, url) values ($guid, '$name', '$description', '$url')"; + $result = insert_data($query); + + if ($result !== false) { $entity = get_entity($guid); - if (trigger_elgg_event('create',$entity->type,$entity)) { + if (elgg_trigger_event('create', $entity->type, $entity)) { return $guid; } else { $entity->delete(); @@ -309,27 +110,14 @@ function create_site_entity($guid, $name, $description, $url) { } /** - * THIS FUNCTION IS DEPRECATED. - * - * Delete a site's extra data. - * - * @param int $guid - */ -function delete_site_entity($guid) { - system_message(sprintf(elgg_echo('deprecatedfunction'), 'delete_user_entity')); - - return 1; // Always return that we have deleted one row in order to not break existing code. -} - -/** * Add a user to a site. * - * @param int $site_guid - * @param int $user_guid + * @param int $site_guid Site guid + * @param int $user_guid User guid + * + * @return bool */ function add_site_user($site_guid, $user_guid) { - global $CONFIG; - $site_guid = (int)$site_guid; $user_guid = (int)$user_guid; @@ -339,8 +127,10 @@ function add_site_user($site_guid, $user_guid) { /** * Remove a user from a site. * - * @param int $site_guid - * @param int $user_guid + * @param int $site_guid Site GUID + * @param int $user_guid User GUID + * + * @return bool */ function remove_site_user($site_guid, $user_guid) { $site_guid = (int)$site_guid; @@ -350,62 +140,14 @@ function remove_site_user($site_guid, $user_guid) { } /** - * Get the members of a site. - * - * @param int $site_guid - * @param int $limit - * @param int $offset - */ -function get_site_members($site_guid, $limit = 10, $offset = 0) { - $site_guid = (int)$site_guid; - $limit = (int)$limit; - $offset = (int)$offset; - - return elgg_get_entities_from_relationship(array( - 'relationship' => 'member_of_site', - 'relationship_guid' => $site_guid, - 'inverse_relationship' => TRUE, - 'types' => 'user', - 'limit' => $limit, 'offset' => $offset - )); -} - -/** - * Display a list of site members - * - * @param int $site_guid The GUID of the site - * @param int $limit The number of members to display on a page - * @param true|false $fullview Whether or not to display the full view (default: true) - * @return string A displayable list of members - */ -function list_site_members($site_guid, $limit = 10, $fullview = true) { - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $options = array( - 'relationship' => 'member_of_site', - 'relationship_guid' => $site_guid, - 'inverse_relationship' => TRUE, - 'types' => 'user', - 'limit' => $limit, - 'offset' => $offset, - 'count' => TRUE - ); - $count = (int) elgg_get_entities_from_relationship($options); - $entities = get_site_members($site_guid, $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview); - -} - -/** * Add an object to a site. * - * @param int $site_guid - * @param int $object_guid + * @param int $site_guid Site GUID + * @param int $object_guid Object GUID + * + * @return mixed */ function add_site_object($site_guid, $object_guid) { - global $CONFIG; - $site_guid = (int)$site_guid; $object_guid = (int)$object_guid; @@ -415,8 +157,10 @@ function add_site_object($site_guid, $object_guid) { /** * Remove an object from a site. * - * @param int $site_guid - * @param int $object_guid + * @param int $site_guid Site GUID + * @param int $object_guid Object GUID + * + * @return bool */ function remove_site_object($site_guid, $object_guid) { $site_guid = (int)$site_guid; @@ -428,77 +172,24 @@ function remove_site_object($site_guid, $object_guid) { /** * Get the objects belonging to a site. * - * @param int $site_guid - * @param string $subtype - * @param int $limit - * @param int $offset + * @param int $site_guid Site GUID + * @param string $subtype Subtype + * @param int $limit Limit + * @param int $offset Offset + * + * @return mixed */ function get_site_objects($site_guid, $subtype = "", $limit = 10, $offset = 0) { $site_guid = (int)$site_guid; - $subtype = sanitise_string($subtype); $limit = (int)$limit; $offset = (int)$offset; return elgg_get_entities_from_relationship(array( 'relationship' => 'member_of_site', - 'relationship_guid' => $site_guid, - 'inverse_relationship' => TRUE, - 'types' => 'object', - 'subtypes' => $subtype, - 'limit' => $limit, - 'offset' => $offset - )); -} - -/** - * Add a collection to a site. - * - * @param int $site_guid - * @param int $collection_guid - */ -function add_site_collection($site_guid, $collection_guid) { - global $CONFIG; - - $site_guid = (int)$site_guid; - $collection_guid = (int)$collection_guid; - - return add_entity_relationship($collection_guid, "member_of_site", $site_guid); -} - -/** - * Remove a collection from a site. - * - * @param int $site_guid - * @param int $collection_guid - */ -function remove_site_collection($site_guid, $collection_guid) { - $site_guid = (int)$site_guid; - $collection_guid = (int)$collection_guid; - - return remove_entity_relationship($collection_guid, "member_of_site", $site_guid); -} - -/** - * Get the collections belonging to a site. - * - * @param int $site_guid - * @param string $subtype - * @param int $limit - * @param int $offset - */ -function get_site_collections($site_guid, $subtype = "", $limit = 10, $offset = 0) { - $site_guid = (int)$site_guid; - $subtype = sanitise_string($subtype); - $limit = (int)$limit; - $offset = (int)$offset; - - // collection isn't a valid type. This won't work. - return elgg_get_entities_from_relationship(array( - 'relationship' => 'member_of_site', - 'relationship_guid' => $site_guid, - 'inverse_relationship' => TRUE, - 'types' => 'collection', - 'subtypes' => $subtype, + 'relationship_guid' => $site_guid, + 'inverse_relationship' => TRUE, + 'type' => 'object', + 'subtype' => $subtype, 'limit' => $limit, 'offset' => $offset )); @@ -506,6 +197,10 @@ function get_site_collections($site_guid, $subtype = "", $limit = 10, $offset = /** * Return the site via a url. + * + * @param string $url The URL of a site + * + * @return mixed */ function get_site_by_url($url) { global $CONFIG; @@ -515,63 +210,18 @@ function get_site_by_url($url) { $row = get_data_row("SELECT * from {$CONFIG->dbprefix}sites_entity where url='$url'"); if ($row) { - return new ElggSite($row); - } - - return false; -} - -/** - * Searches for a site based on a complete or partial name or description or url using full text searching. - * - * IMPORTANT NOTE: With MySQL's default setup: - * 1) $criteria must be 4 or more characters long - * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! - * - * @param string $criteria The partial or full name or username. - * @param int $limit Limit of the search. - * @param int $offset Offset. - * @param string $order_by The order. - * @param boolean $count Whether to return the count of results or just the results. - * @deprecated 1.7 - */ -function search_for_site($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { - elgg_deprecated_notice('search_for_site() was deprecated by new search plugin.', 1.7); - global $CONFIG; - - $criteria = sanitise_string($criteria); - $limit = (int)$limit; - $offset = (int)$offset; - $order_by = sanitise_string($order_by); - - $access = get_access_sql_suffix("e"); - - if ($order_by == "") { - $order_by = "e.time_created desc"; + return get_entity($row->guid); } - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT e.* "; - } - $query .= "from {$CONFIG->dbprefix}entities e join {$CONFIG->dbprefix}sites_entity s on e.guid=s.guid where match(s.name,s.description,s.url) against ('$criteria') and $access"; - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } - } return false; } /** * Retrieve a site and return the domain portion of its url. * - * @param int $guid + * @param int $guid ElggSite GUID + * + * @return string */ function get_site_domain($guid) { $guid = (int)$guid; @@ -586,44 +236,21 @@ function get_site_domain($guid) { } /** - * Initialise site handling + * Unit tests for sites * - * Called at the beginning of system running, to set the ID of the current site. - * This is 0 by default, but plugins may alter this behaviour by attaching functions - * to the sites init event and changing $CONFIG->site_id. + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params * - * @uses $CONFIG - * @param string $event Event API required parameter - * @param string $object_type Event API required parameter - * @param null $object Event API required parameter - * @return true + * @return array + * @access private */ -function sites_init($event, $object_type, $object) { - global $CONFIG; - - if (is_installed() && is_db_installed()) { - $site = trigger_plugin_hook("siteid","system"); - if ($site === null || $site === false) { - $CONFIG->site_id = (int) datalist_get('default_site'); - } else { - $CONFIG->site_id = $site; - } - $CONFIG->site_guid = $CONFIG->site_id; - $CONFIG->site = get_entity($CONFIG->site_guid); - - return true; - } - - return true; -} - -// Register event handlers -register_elgg_event_handler('boot','system','sites_init',2); - -// Register with unit test -register_plugin_hook('unit_test', 'system', 'sites_test'); function sites_test($hook, $type, $value, $params) { global $CONFIG; $value[] = "{$CONFIG->path}engine/tests/objects/sites.php"; return $value; } + +// Register with unit test +elgg_register_plugin_hook_handler('unit_test', 'system', 'sites_test'); diff --git a/engine/lib/social.php b/engine/lib/social.php deleted file mode 100644 index 381c7ea4f..000000000 --- a/engine/lib/social.php +++ /dev/null @@ -1,119 +0,0 @@ -<?php -/** - * Elgg Social - * Functions and objects which provide powerful social aspects within Elgg - * - * @package Elgg - * @subpackage Core - * @author Curverider - * @link http://elgg.org/ - -/** - * Filters a string into an array of significant words - * - * @param string $string - * @return array - */ -function filter_string($string) { - // Convert it to lower and trim - $string = strtolower($string); - $string = trim($string); - - // Remove links and email addresses - // match protocol://address/path/file.extension?some=variable&another=asf% - $string = preg_replace("/\s([a-zA-Z]+:\/\/[a-z][a-z0-9\_\.\-]*[a-z]{2,6}[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu"," ", $string); - // match www.something.domain/path/file.extension?some=variable&another=asf% - $string = preg_replace("/\s(www\.[a-z][a-z0-9\_\.\-]*[a-z]{2,6}[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu"," ", $string); - // match name@address - $string = preg_replace("/\s([a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]*\@[a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]{2,6})([\s|\.|\,])/iu"," ", $string); - - // Sanitise the string; remove unwanted characters - $string = preg_replace('/\W/ui', ' ', $string); - - // Explode it into an array - $terms = explode(' ',$string); - - // Remove any blacklist terms - //$terms = array_filter($terms, 'remove_blacklist'); - - return $terms; -} - -/** - * Returns true if the word in $input is considered significant - * - * @param string $input - * @return true|false - */ -function remove_blacklist($input) { - global $CONFIG; - - if (!is_array($CONFIG->wordblacklist)) { - return $input; - } - - if (strlen($input) < 3 || in_array($input,$CONFIG->wordblacklist)) { - return false; - } - - return true; -} - - -/** - * Initialise. - * - * Sets a blacklist of words in the current language. This is a comma separated list in word:blacklist. - */ -function social_init() { - global $CONFIG; - - $CONFIG->wordblacklist = array(); - - $list = explode(',', elgg_echo('word:blacklist')); - if ($list) { - foreach ($list as $l) { - $CONFIG->wordblacklist[] = trim($l); - } - } else { - // Fallback - shouldn't happen - $CONFIG->wordblacklist = array( - 'and', - 'the', - 'then', - 'but', - 'she', - 'his', - 'her', - 'him', - 'one', - 'not', - 'also', - 'about', - 'now', - 'hence', - 'however', - 'still', - 'likewise', - 'otherwise', - 'therefore', - 'conversely', - 'rather', - 'consequently', - 'furthermore', - 'nevertheless', - 'instead', - 'meanwhile', - 'accordingly', - 'this', - 'seems', - 'what', - 'whom', - 'whose', - 'whoever', - 'whomever', - ); - } -} - -register_elgg_event_handler("init","system","social_init");
\ No newline at end of file diff --git a/engine/lib/statistics.php b/engine/lib/statistics.php index b8bf2b012..4cb0bb0b8 100644 --- a/engine/lib/statistics.php +++ b/engine/lib/statistics.php @@ -1,20 +1,20 @@ <?php /** * Elgg statistics library. + * * This file contains a number of functions for obtaining statistics about the running system. - * These statistics are mainly used by the administration pages, and is also where the basic views for statistics - * are added. + * These statistics are mainly used by the administration pages, and is also where the basic + * views for statistics are added. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Statistics */ /** * Return an array reporting the number of various entities in the system. * * @param int $owner_guid Optional owner of the statistics + * * @return array */ function get_entity_statistics($owner_guid = 0) { @@ -23,7 +23,10 @@ function get_entity_statistics($owner_guid = 0) { $entity_stats = array(); $owner_guid = (int)$owner_guid; - $query = "SELECT distinct e.type,s.subtype,e.subtype as subtype_id from {$CONFIG->dbprefix}entities e left join {$CONFIG->dbprefix}entity_subtypes s on e.subtype=s.id"; + $query = "SELECT distinct e.type,s.subtype,e.subtype as subtype_id + from {$CONFIG->dbprefix}entities e left + join {$CONFIG->dbprefix}entity_subtypes s on e.subtype=s.id"; + $owner_query = ""; if ($owner_guid) { $query .= " where owner_guid=$owner_guid"; @@ -39,9 +42,11 @@ function get_entity_statistics($owner_guid = 0) { $entity_stats[$type->type] = array(); } - $query = "SELECT count(*) as count from {$CONFIG->dbprefix}entities where type='{$type->type}' $owner_query"; + $query = "SELECT count(*) as count + from {$CONFIG->dbprefix}entities where type='{$type->type}' $owner_query"; + if ($type->subtype) { - $query.= " and subtype={$type->subtype_id}"; + $query .= " and subtype={$type->subtype_id}"; } $subtype_cnt = get_data_row($query); @@ -59,7 +64,8 @@ function get_entity_statistics($owner_guid = 0) { /** * Return the number of users registered in the system. * - * @param bool $show_deactivated + * @param bool $show_deactivated Count not enabled users? + * * @return int */ function get_number_users($show_deactivated = false) { @@ -71,7 +77,10 @@ function get_number_users($show_deactivated = false) { $access = "and " . get_access_sql_suffix(); } - $result = get_data_row("SELECT count(*) as count from {$CONFIG->dbprefix}entities where type='user' $access"); + $query = "SELECT count(*) as count + from {$CONFIG->dbprefix}entities where type='user' $access"; + + $result = get_data_row($query); if ($result) { return $result->count; @@ -82,28 +91,36 @@ function get_number_users($show_deactivated = false) { /** * Return a list of how many users are currently online, rendered as a view. + * + * @return string */ function get_online_users() { - $offset = get_input('offset', 0); - $count = count(find_active_users(600, 9999)); - $objects = find_active_users(600, 10, $offset); + $limit = max(0, (int) get_input("limit", 10)); + $offset = max(0, (int) get_input("offset", 0)); + + $count = find_active_users(600, $limit, $offset, true); + $objects = find_active_users(600, $limit, $offset); if ($objects) { - return elgg_view_entity_list($objects, $count,$offset,10,false); + return elgg_view_entity_list($objects, array( + 'count' => $count, + 'limit' => $limit, + 'offset' => $offset + )); } + return ''; } /** * Initialise the statistics admin page. + * + * @return void + * @access private */ function statistics_init() { - extend_elgg_admin_page('admin/statistics_opt/basic', 'admin/statistics'); - extend_elgg_admin_page('admin/statistics_opt/numentities', 'admin/statistics'); - extend_elgg_admin_page('admin/statistics_opt/online', 'admin/statistics'); - - extend_elgg_settings_page('usersettings/statistics_opt/online', 'usersettings/statistics'); - extend_elgg_settings_page('usersettings/statistics_opt/numentities', 'usersettings/statistics'); + elgg_extend_view('core/settings/statistics', 'core/settings/statistics/online'); + elgg_extend_view('core/settings/statistics', 'core/settings/statistics/numentities'); } /// Register init function -register_elgg_event_handler('init','system','statistics_init');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'statistics_init'); diff --git a/engine/lib/system_log.php b/engine/lib/system_log.php index 75a7cc531..84302632e 100644 --- a/engine/lib/system_log.php +++ b/engine/lib/system_log.php @@ -3,110 +3,72 @@ * Elgg system log. * Listens to events and writes crud events into the system log database. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Logging */ /** - * Interface that provides an interface which must be implemented by all objects wishing to be - * recorded in the system log (and by extension the river). - * - * This interface defines a set of methods that permit the system log functions to hook in and retrieve - * the necessary information and to identify what events can actually be logged. + * Retrieve the system log based on a number of parameters. * - * To have events involving your object to be logged simply implement this interface. + * @todo too many args, and the first arg is too confusing * - * @author Curverider Ltd + * @param int|array $by_user The guid(s) of the user(s) who initiated the event. + * Use 0 for unowned entries. Anything else falsey means anyone. + * @param string $event The event you are searching on. + * @param string $class The class of object it effects. + * @param string $type The type + * @param string $subtype The subtype. + * @param int $limit Maximum number of responses to return. + * @param int $offset Offset of where to start. + * @param bool $count Return count or not + * @param int $timebefore Lower time limit + * @param int $timeafter Upper time limit + * @param int $object_id GUID of an object + * @param string $ip_address The IP address. + * @return mixed */ -interface Loggable { - /** - * Return an identification for the object for storage in the system log. - * This id must be an integer. - * - * @return int - */ - public function getSystemLogID(); - - /** - * Return the class name of the object. - * Added as a function because get_class causes errors for some reason. - */ - public function getClassName(); - - /** - * Return the type of the object - eg. object, group, user, relationship, metadata, annotation etc - */ - public function getType(); - - /** - * Return a subtype. For metadata & annotations this is the 'name' and for relationship this is the relationship type. - */ - public function getSubtype(); - - /** - * 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 the GUID of the owner of this object. - */ - public function getObjectOwnerGUID(); -} +function get_system_log($by_user = "", $event = "", $class = "", $type = "", $subtype = "", $limit = 10, + $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $object_id = 0, + $ip_address = "") { -/** - * Retrieve the system log based on a number of parameters. - * - * @param int or array $by_user The guid(s) of the user(s) who initiated the event. - * @param string $event The event you are searching on. - * @param string $class The class of object it effects. - * @param string $type The type - * @param string $subtype The subtype. - * @param int $limit Maximum number of responses to return. - * @param int $offset Offset of where to start. - * @param bool $count Return count or not - */ -function get_system_log($by_user = "", $event = "", $class = "", $type = "", $subtype = "", $limit = 10, $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $object_id = 0) { global $CONFIG; $by_user_orig = $by_user; if (is_array($by_user) && sizeof($by_user) > 0) { - foreach($by_user as $key => $val) { + foreach ($by_user as $key => $val) { $by_user[$key] = (int) $val; } } else { $by_user = (int)$by_user; } + $event = sanitise_string($event); $class = sanitise_string($class); $type = sanitise_string($type); $subtype = sanitise_string($subtype); + $ip_address = sanitise_string($ip_address); $limit = (int)$limit; $offset = (int)$offset; $where = array(); - if ($by_user_orig!=="") { + if ($by_user_orig !== "" && $by_user_orig !== false && $by_user_orig !== null) { if (is_int($by_user)) { $where[] = "performed_by_guid=$by_user"; } else if (is_array($by_user)) { - $where [] = "performed_by_guid in (". implode(",",$by_user) .")"; + $where [] = "performed_by_guid in (" . implode(",", $by_user) . ")"; } } if ($event != "") { $where[] = "event='$event'"; } - if ($class!=="") { + if ($class !== "") { $where[] = "object_class='$class'"; } if ($type != "") { $where[] = "object_type='$type'"; } - if ($subtype!=="") { + if ($subtype !== "") { $where[] = "object_subtype='$subtype'"; } @@ -119,6 +81,9 @@ function get_system_log($by_user = "", $event = "", $class = "", $type = "", $su if ($object_id) { $where[] = "object_id = " . ((int) $object_id); } + if ($ip_address) { + $where[] = "ip_address = '$ip_address'"; + } $select = "*"; if ($count) { @@ -135,7 +100,8 @@ function get_system_log($by_user = "", $event = "", $class = "", $type = "", $su } if ($count) { - if ($numrows = get_data_row($query)) { + $numrows = get_data_row($query); + if ($numrows) { return $numrows->count; } } else { @@ -149,6 +115,8 @@ function get_system_log($by_user = "", $event = "", $class = "", $type = "", $su * Return a specific log entry. * * @param int $entry_id The log entry + * + * @return mixed */ function get_log_entry($entry_id) { global $CONFIG; @@ -162,15 +130,20 @@ function get_log_entry($entry_id) { * Return the object referred to by a given log entry * * @param int $entry_id The log entry + * + * @return mixed */ function get_object_from_log_entry($entry_id) { $entry = get_log_entry($entry_id); if ($entry) { $class = $entry->object_class; - $tmp = new $class(); - $object = $tmp->getObjectFromID($entry->object_id); - + // surround with try/catch because object could be disabled + try { + $object = new $class($entry->object_id); + } catch (Exception $e) { + + } if ($object) { return $object; } @@ -184,16 +157,27 @@ function get_object_from_log_entry($entry_id) { * * This is called by the event system and should not be called directly. * - * @param $object The object you're talking about. - * @param $event String The event being logged + * @param object $object The object you're talking about. + * @param string $event The event being logged + * @return void */ function system_log($object, $event) { global $CONFIG; - static $logcache; + static $log_cache; + static $cache_size = 0; if ($object instanceof Loggable) { - if (!is_array($logcache)) { - $logcache = array(); + + /* @var ElggEntity|ElggExtender $object */ + if (datalist_get('version') < 2012012000) { + // this is a site that doesn't have the ip_address column yet + return; + } + + // reset cache if it has grown too large + if (!is_array($log_cache) || $cache_size > 500) { + $log_cache = array(); + $cache_size = 0; } // Has loggable interface, extract the necessary information and store @@ -203,7 +187,17 @@ function system_log($object, $event) { $object_subtype = $object->getSubtype(); $event = sanitise_string($event); $time = time(); - $performed_by = (int)$_SESSION['guid']; + + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip_address = array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])); + } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) { + $ip_address = array_pop(explode(',', $_SERVER['HTTP_X_REAL_IP'])); + } else { + $ip_address = $_SERVER['REMOTE_ADDR']; + } + $ip_address = sanitise_string($ip_address); + + $performed_by = elgg_get_logged_in_user_guid(); if (isset($object->access_id)) { $access_id = $object->access_id; @@ -223,13 +217,19 @@ function system_log($object, $event) { } // Create log if we haven't already created it - if (!isset($logcache[$time][$object_id][$event])) { - insert_data("INSERT DELAYED into {$CONFIG->dbprefix}system_log (object_id, object_class, object_type, object_subtype, event, performed_by_guid, owner_guid, access_id, enabled, time_created) VALUES ('$object_id','$object_class','$object_type', '$object_subtype', '$event',$performed_by, $owner_guid, $access_id, '$enabled', '$time')"); - - $logcache[$time][$object_id][$event] = true; + if (!isset($log_cache[$time][$object_id][$event])) { + $query = "INSERT DELAYED into {$CONFIG->dbprefix}system_log + (object_id, object_class, object_type, object_subtype, event, + performed_by_guid, owner_guid, access_id, enabled, time_created, ip_address) + VALUES + ('$object_id','$object_class','$object_type', '$object_subtype', '$event', + $performed_by, $owner_guid, $access_id, '$enabled', '$time', '$ip_address')"; + + insert_data($query); + + $log_cache[$time][$object_id][$event] = true; + $cache_size += 1; } - - return true; } } @@ -237,6 +237,8 @@ function system_log($object, $event) { * This function creates an archive copy of the system log. * * @param int $offset An offset in seconds from now to archive (useful for log rotation) + * + * @return bool */ function archive_log($offset = 0) { global $CONFIG; @@ -247,7 +249,10 @@ function archive_log($offset = 0) { $ts = $now - $offset; // create table - if (!update_data("CREATE TABLE {$CONFIG->dbprefix}system_log_$now as SELECT * from {$CONFIG->dbprefix}system_log WHERE time_created<$ts")) { + $query = "CREATE TABLE {$CONFIG->dbprefix}system_log_$now as + SELECT * from {$CONFIG->dbprefix}system_log WHERE time_created<$ts"; + + if (!update_data($query)) { return false; } @@ -268,10 +273,11 @@ function archive_log($offset = 0) { /** * Default system log handler, allows plugins to override, extend or disable logging. * - * @param string $event - * @param string $object_type - * @param Loggable $object - * @return unknown + * @param string $event Event name + * @param string $object_type Object type + * @param Loggable $object Object to log + * + * @return true */ function system_log_default_logger($event, $object_type, $object) { system_log($object['object'], $object['event']); @@ -283,20 +289,23 @@ function system_log_default_logger($event, $object_type, $object) { * System log listener. * This function listens to all events in the system and logs anything appropriate. * - * @param String $event - * @param String $object_type - * @param Loggable $object + * @param String $event Event name + * @param String $object_type Type of object + * @param Loggable $object Object to log + * + * @return true + * @access private */ function system_log_listener($event, $object_type, $object) { - if (($object_type!='systemlog') && ($event!='log')) { - trigger_elgg_event('log', 'systemlog', array('object' => $object, 'event' => $event)); + if (($object_type != 'systemlog') && ($event != 'log')) { + elgg_trigger_event('log', 'systemlog', array('object' => $object, 'event' => $event)); } return true; } /** Register event to listen to all events **/ -register_elgg_event_handler('all','all','system_log_listener', 400); +elgg_register_event_handler('all', 'all', 'system_log_listener', 400); /** Register a default system log handler */ -register_elgg_event_handler('log','systemlog','system_log_default_logger', 999);
\ No newline at end of file +elgg_register_event_handler('log', 'systemlog', 'system_log_default_logger', 999); diff --git a/engine/lib/tags.php b/engine/lib/tags.php index ea8d3ebcc..586a9b9e4 100644 --- a/engine/lib/tags.php +++ b/engine/lib/tags.php @@ -3,28 +3,33 @@ * Elgg tags * Functions for managing tags and tag clouds. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Tags */ - /** * The algorithm working out the size of font based on the number of tags. * This is quick and dirty. + * + * @param int $min Min size + * @param int $max Max size + * @param int $number_of_tags The number of tags + * @param int $buckets The number of buckets + * + * @return int + * @access private */ function calculate_tag_size($min, $max, $number_of_tags, $buckets = 6) { - $delta = (($max - $min) / $buckets); + $delta = (($max - $min) / $buckets); $thresholds = array(); - for ($n=1; $n <= $buckets; $n++) { - $thresholds[$n-1] = ($min + $n) * $delta; + for ($n = 1; $n <= $buckets; $n++) { + $thresholds[$n - 1] = ($min + $n) * $delta; } // Correction - if ($thresholds[$buckets-1]>$max) { - $thresholds[$buckets-1] = $max; + if ($thresholds[$buckets - 1] > $max) { + $thresholds[$buckets - 1] = $max; } $size = 0; @@ -40,8 +45,11 @@ function calculate_tag_size($min, $max, $number_of_tags, $buckets = 6) { /** * This function generates an array of tags with a weighting. * - * @param array $tags The array of tags. - * @return An associated array of tags with a weighting, this can then be mapped to a display class. + * @param array $tags The array of tags. + * @param int $buckets The number of buckets + * + * @return array An associated array of tags with a weighting, this can then be mapped to a display class. + * @access private */ function generate_tag_cloud(array $tags, $buckets = 6) { $cloud = array(); @@ -52,11 +60,11 @@ function generate_tag_cloud(array $tags, $buckets = 6) { foreach ($tags as $tag) { $cloud[$tag]++; - if ($cloud[$tag]>$max) { + if ($cloud[$tag] > $max) { $max = $cloud[$tag]; } - if ($cloud[$tag]<$min) { + if ($cloud[$tag] < $min) { $min = $cloud[$tag]; } } @@ -69,125 +77,205 @@ function generate_tag_cloud(array $tags, $buckets = 6) { } /** - * Get an array of tags with weights for use with the output/tagcloud view. - * - * @param int $threshold Get the threshold of minimum number of each tags to bother with (ie only show tags where there are more than $threshold occurances) - * @param int $limit Number of tags to return - * @param string $metadata_name Optionally, the name of the field you want to grab for - * @param string $entity_type Optionally, the entity type ('object' etc) - * @param string $entity_subtype The entity subtype, optionally - * @param int $owner_guid The GUID of the tags owner, optionally - * @param int $site_guid Optionally, the site to restrict to (default is the current site) - * @param int $start_ts Optionally specify a start timestamp for tags used to generate cloud. - * @param int $ent_ts Optionally specify an end timestamp for tags used to generate cloud. - * @return array|false Array of objects with ->tag and ->total values, or false on failure + * Get popular tags and their frequencies + * + * Supports similar arguments as elgg_get_entities() + * + * @param array $options Array in format: + * + * threshold => INT minimum tag count + * + * tag_names => array() metadata tag names - must be registered tags + * + * limit => INT number of tags to return + * + * types => NULL|STR entity type (SQL: type = '$type') + * + * subtypes => NULL|STR entity subtype (SQL: subtype = '$subtype') + * + * type_subtype_pairs => NULL|ARR (array('type' => 'subtype')) + * (SQL: type = '$type' AND subtype = '$subtype') pairs + * + * owner_guids => NULL|INT entity guid + * + * container_guids => NULL|INT container_guid + * + * site_guids => NULL (current_site)|INT site_guid + * + * created_time_lower => NULL|INT Created time lower boundary in epoch time + * + * created_time_upper => NULL|INT Created 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 + * + * wheres => array() Additional where clauses to AND together + * + * joins => array() Additional joins + * + * @return object[]|false If no tags or error, false + * otherwise, array of objects with ->tag and ->total values + * @since 1.7.1 */ - -function get_tags($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", $entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { +function elgg_get_tags(array $options = array()) { global $CONFIG; - $threshold = (int) $threshold; - $limit = (int) $limit; + $defaults = array( + 'threshold' => 1, + 'tag_names' => array(), + 'limit' => 10, + + 'types' => ELGG_ENTITIES_ANY_VALUE, + 'subtypes' => ELGG_ENTITIES_ANY_VALUE, + 'type_subtype_pairs' => 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, + + 'joins' => array(), + 'wheres' => array(), + ); + + + $options = array_merge($defaults, $options); + + $singulars = array('type', 'subtype', 'owner_guid', 'container_guid', 'site_guid', 'tag_name'); + $options = elgg_normalise_plural_options_array($options, $singulars); $registered_tags = elgg_get_registered_tag_metadata_names(); - if (!in_array($metadata_name, $registered_tags)) { - elgg_deprecated_notice('Tag metadata names must be registered by elgg_register_tag_metadata_name()', 1.7); - } - if (!empty($metadata_name)) { - $metadata_name = (int) get_metastring_id($metadata_name); - // test if any metadata with that name - if (!$metadata_name) { - return false; // no matches so short circuit - } - } else { - $metadata_name = 0; - } - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - $entity_type = sanitise_string($entity_type); - - if ($owner_guid != "") { - if (is_array($owner_guid)) { - foreach($owner_guid as $key => $val) { - $owner_guid[$key] = (int) $val; - } - } else { - $owner_guid = (int) $owner_guid; - } + if (!is_array($options['tag_names'])) { + return false; } - if ($site_guid < 0) { - $site_guid = $CONFIG->site_id; + // empty array so use all registered tag names + if (count($options['tag_names']) == 0) { + $options['tag_names'] = $registered_tags; } - $query = "SELECT msvalue.string as tag, count(msvalue.id) as total "; - $query .= "FROM {$CONFIG->dbprefix}entities e join {$CONFIG->dbprefix}metadata md on md.entity_guid = e.guid "; - if ($entity_subtype > 0) { - $query .= " join {$CONFIG->dbprefix}entity_subtypes subtype on subtype.id = e.subtype "; + $diff = array_diff($options['tag_names'], $registered_tags); + if (count($diff) > 0) { + elgg_deprecated_notice('Tag metadata names must be registered by elgg_register_tag_metadata_name()', 1.7); + // return false; } - $query .= " join {$CONFIG->dbprefix}metastrings msvalue on msvalue.id = md.value_id "; - $query .= " where msvalue.string != '' "; - if ($metadata_name > 0) { - $query .= " and md.name_id = {$metadata_name} "; - } - if ($site_guid > 0) { - $query .= " and e.site_guid = {$site_guid} "; - } - if ($entity_subtype > 0) { - $query .= " and e.subtype = {$entity_subtype} "; + $wheres = $options['wheres']; + + // catch for tags that were spaces + $wheres[] = "msv.string != ''"; + + $sanitised_tags = array(); + foreach ($options['tag_names'] as $tag) { + $sanitised_tags[] = '"' . sanitise_string($tag) . '"'; } - if ($entity_type != "") { - $query .= " and e.type = '{$entity_type}' "; + $tags_in = implode(',', $sanitised_tags); + $wheres[] = "(msn.string IN ($tags_in))"; + + $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.site_guid', $options['site_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_entity_time_where_sql('e', $options['created_time_upper'], + $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']); + + // see if any functions failed + // remove empty strings on successful functions + foreach ($wheres as $i => $where) { + if ($where === FALSE) { + return FALSE; + } elseif (empty($where)) { + unset($wheres[$i]); + } } - if (is_array($owner_guid)) { - $query .= " and e.container_guid in (".implode(",",$owner_guid).")"; - } else if (is_int($owner_guid)) { - $query .= " and e.container_guid = {$owner_guid} "; + + // remove identical where clauses + $wheres = array_unique($wheres); + + $joins = $options['joins']; + + $joins[] = "JOIN {$CONFIG->dbprefix}metadata md on md.entity_guid = e.guid"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings msv on msv.id = md.value_id"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings msn on md.name_id = msn.id"; + + // remove identical join clauses + $joins = array_unique($joins); + + foreach ($joins as $i => $join) { + if ($join === FALSE) { + return FALSE; + } elseif (empty($join)) { + unset($joins[$i]); + } } - if ($start_ts) { - $start_ts = (int)$start_ts; - $query .= " and e.time_created>=$start_ts"; + + + $query = "SELECT msv.string as tag, count(msv.id) as total "; + $query .= "FROM {$CONFIG->dbprefix}entities e "; + + // add joins + foreach ($joins as $j) { + $query .= " $j "; } - if ($end_ts) { - $end_ts = (int)$end_ts; - $query .= " and e.time_created<=$end_ts"; + // add wheres + $query .= ' WHERE '; + + foreach ($wheres as $w) { + $query .= " $w AND "; } // Add access controls - $query .= ' and ' . get_access_sql_suffix("e"); + $query .= get_access_sql_suffix('e'); - $query .= " group by msvalue.string having total > {$threshold} order by total desc limit {$limit} "; + $threshold = sanitise_int($options['threshold']); + $query .= " GROUP BY msv.string HAVING total >= {$threshold} "; + $query .= " ORDER BY total DESC "; + + $limit = sanitise_int($options['limit']); + $query .= " LIMIT {$limit} "; return get_data($query); } /** - * Loads and displays a tagcloud given particular criteria. - * - * @param int $threshold Get the threshold of minimum number of each tags to bother with (ie only show tags where there are more than $threshold occurances) - * @param int $limit Number of tags to return - * @param string $metadata_name Optionally, the name of the field you want to grab for - * @param string $entity_type Optionally, the entity type ('object' etc) - * @param string $entity_subtype The entity subtype, optionally - * @param int $owner_guid The GUID of the tags owner, optionally - * @param int $site_guid Optionally, the site to restrict to (default is the current site) - * @param int $start_ts Optionally specify a start timestamp for tags used to generate cloud. - * @param int $ent_ts Optionally specify an end timestamp for tags used to generate cloud. - * @return string THe HTML (or other, depending on view type) of the tagcloud. + * Returns viewable tagcloud + * + * @see elgg_get_tags + * + * @param array $options Any elgg_get_tags() options except: + * + * type => must be single entity type + * + * subtype => must be single entity subtype + * + * @return string + * @since 1.7.1 */ +function elgg_view_tagcloud(array $options = array()) { -function display_tagcloud($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", $entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { - - if (!in_array($metadata_name, $registered_tags)) { - elgg_deprecated_notice('Tag metadata names must be registered by elgg_register_tag_metadata_name()', 1.7); + $type = $subtype = ''; + if (isset($options['type'])) { + $type = $options['type']; + } + if (isset($options['subtype'])) { + $subtype = $options['subtype']; } - return elgg_view("output/tagcloud",array('value' => get_tags($threshold, $limit, $metadata_name, $entity_type, $entity_subtype, $owner_guid, $site_guid, $start_ts, $end_ts), - 'object' => $entity_type, - 'subtype' => $entity_subtype)); + $tag_data = elgg_get_tags($options); + return elgg_view("output/tagcloud", array( + 'value' => $tag_data, + 'type' => $type, + 'subtype' => $subtype, + )); } /** @@ -195,10 +283,10 @@ function display_tagcloud($threshold = 1, $limit = 10, $metadata_name = "", $ent * This is required if you are using a non-standard metadata name * for your tags. * - * @since 1.7 + * @param string $name Tag name * - * @param string $name - * @return TRUE + * @return bool + * @since 1.7.0 */ function elgg_register_tag_metadata_name($name) { global $CONFIG; @@ -217,16 +305,50 @@ function elgg_register_tag_metadata_name($name) { /** * Returns an array of valid metadata names for tags. * - * @since 1.7 - * * @return array + * @since 1.7.0 */ function elgg_get_registered_tag_metadata_names() { global $CONFIG; - $names = (isset($CONFIG->registered_tag_metadata_names)) ? $CONFIG->registered_tag_metadata_names : array(); + $names = (isset($CONFIG->registered_tag_metadata_names)) + ? $CONFIG->registered_tag_metadata_names : array(); + return $names; } -// register the standard tags metadata name -elgg_register_tag_metadata_name('tags');
\ No newline at end of file +/** + * Page hander for tags + * + * @param array $page Page array + * + * @return bool + * @access private + */ +function elgg_tagcloud_page_handler($page) { + + $title = elgg_view_title(elgg_echo('tags:site_cloud')); + $options = array( + 'threshold' => 0, + 'limit' => 100, + 'tag_name' => 'tags', + ); + $tags = elgg_view_tagcloud($options); + $content = $title . $tags; + $body = elgg_view_layout('one_sidebar', array('content' => $content)); + + echo elgg_view_page(elgg_echo('tags:site_cloud'), $body); + return true; +} + +/** + * @access private + */ +function elgg_tags_init() { + // register the standard tags metadata name + elgg_register_tag_metadata_name('tags'); + + elgg_register_page_handler('tags', 'elgg_tagcloud_page_handler'); +} + +elgg_register_event_handler('init', 'system', 'elgg_tags_init');
\ No newline at end of file diff --git a/engine/lib/upgrade.php b/engine/lib/upgrade.php new file mode 100644 index 000000000..158ec9ec1 --- /dev/null +++ b/engine/lib/upgrade.php @@ -0,0 +1,365 @@ +<?php +/** + * Elgg upgrade library. + * Contains code for handling versioning and upgrades. + * + * @package Elgg.Core + * @subpackage Upgrade + */ + +/** + * Run any php upgrade scripts which are required + * + * @param int $version Version upgrading from. + * @param bool $quiet Suppress errors. Don't use this. + * + * @return bool + * @access private + */ +function upgrade_code($version, $quiet = FALSE) { + // do not remove - upgrade scripts depend on this + global $CONFIG; + + $version = (int) $version; + $upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/'; + $processed_upgrades = elgg_get_processed_upgrades(); + + // upgrading from 1.7 to 1.8. Need to bootstrap. + if (!$processed_upgrades) { + elgg_upgrade_bootstrap_17_to_18(); + + // grab accurate processed upgrades + $processed_upgrades = elgg_get_processed_upgrades(); + } + + $upgrade_files = elgg_get_upgrade_files($upgrade_path); + + if ($upgrade_files === false) { + return false; + } + + $upgrades = elgg_get_unprocessed_upgrades($upgrade_files, $processed_upgrades); + + // Sort and execute + sort($upgrades); + + foreach ($upgrades as $upgrade) { + $upgrade_version = elgg_get_upgrade_file_version($upgrade); + $success = true; + + // hide all errors. + if ($quiet) { + // hide include errors as well as any exceptions that might happen + try { + if (!@include("$upgrade_path/$upgrade")) { + $success = false; + error_log("Could not include $upgrade_path/$upgrade"); + } + } catch (Exception $e) { + $success = false; + error_log($e->getmessage()); + } + } else { + if (!include("$upgrade_path/$upgrade")) { + $success = false; + error_log("Could not include $upgrade_path/$upgrade"); + } + } + + if ($success) { + // incrementally set upgrade so we know where to start if something fails. + $processed_upgrades[] = $upgrade; + + // don't set the version to a lower number in instances where an upgrade + // has been merged from a lower version of Elgg + if ($upgrade_version > $version) { + datalist_set('version', $upgrade_version); + } + + elgg_set_processed_upgrades($processed_upgrades); + } else { + return false; + } + } + + return true; +} + +/** + * Saves the processed upgrades to a dataset. + * + * @param array $processed_upgrades An array of processed upgrade filenames + * (not the path, just the file) + * @return bool + * @access private + */ +function elgg_set_processed_upgrades(array $processed_upgrades) { + $processed_upgrades = array_unique($processed_upgrades); + return datalist_set('processed_upgrades', serialize($processed_upgrades)); +} + +/** + * Gets a list of processes upgrades + * + * @return mixed Array of processed upgrade filenames or false + * @access private + */ +function elgg_get_processed_upgrades() { + $upgrades = datalist_get('processed_upgrades'); + $unserialized = unserialize($upgrades); + return $unserialized; +} + +/** + * Returns the version of the upgrade filename. + * + * @param string $filename The upgrade filename. No full path. + * @return int|false + * @since 1.8.0 + * @access private + */ +function elgg_get_upgrade_file_version($filename) { + preg_match('/^([0-9]{10})([\.a-z0-9-_]+)?\.(php)$/i', $filename, $matches); + + if (isset($matches[1])) { + return (int) $matches[1]; + } + + return false; +} + +/** + * Returns a list of upgrade files relative to the $upgrade_path dir. + * + * @param string $upgrade_path The up + * @return array|false + * @access private + */ +function elgg_get_upgrade_files($upgrade_path = null) { + if (!$upgrade_path) { + $upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/'; + } + $upgrade_path = sanitise_filepath($upgrade_path); + $handle = opendir($upgrade_path); + + if (!$handle) { + return false; + } + + $upgrade_files = array(); + + while ($upgrade_file = readdir($handle)) { + // make sure this is a wellformed upgrade. + if (is_dir($upgrade_path . '$upgrade_file')) { + continue; + } + $upgrade_version = elgg_get_upgrade_file_version($upgrade_file); + if (!$upgrade_version) { + continue; + } + $upgrade_files[] = $upgrade_file; + } + + sort($upgrade_files); + + return $upgrade_files; +} + +/** + * Get the current Elgg version information + * + * @param bool $humanreadable Whether to return a human readable version (default: false) + * + * @return string|false Depending on success + */ +function get_version($humanreadable = false) { + global $CONFIG; + + static $version, $release; + + if (isset($CONFIG->path)) { + if (!isset($version) || !isset($release)) { + if (!include($CONFIG->path . "version.php")) { + return false; + } + } + return (!$humanreadable) ? $version : $release; + } + + return false; +} + +/** + * Checks if any upgrades need to be run. + * + * @param null|array $upgrade_files Optional upgrade files + * @param null|array $processed_upgrades Optional processed upgrades + * + * @return array + * @access private + */ +function elgg_get_unprocessed_upgrades($upgrade_files = null, $processed_upgrades = null) { + if ($upgrade_files === null) { + $upgrade_files = elgg_get_upgrade_files(); + } + + if ($processed_upgrades === null) { + $processed_upgrades = unserialize(datalist_get('processed_upgrades')); + if (!is_array($processed_upgrades)) { + $processed_upgrades = array(); + } + } + + $unprocessed = array_diff($upgrade_files, $processed_upgrades); + return $unprocessed; +} + +/** + * Determines whether or not the database needs to be upgraded. + * + * @return bool Depending on whether or not the db version matches the code version + * @access private + */ +function version_upgrade_check() { + $dbversion = (int) datalist_get('version'); + $version = get_version(); + + if ($version > $dbversion) { + return TRUE; + } + + return FALSE; +} + +/** + * Upgrades Elgg Database and code + * + * @return bool + * @access private + */ +function version_upgrade() { + // It's possible large upgrades could exceed the max execution time. + set_time_limit(0); + + $dbversion = (int) datalist_get('version'); + + // No version number? Oh snap...this is an upgrade from a clean installation < 1.7. + // Run all upgrades without error reporting and hope for the best. + // See https://github.com/elgg/elgg/issues/1432 for more. + $quiet = !$dbversion; + + // Note: Database upgrades are deprecated as of 1.8. Use code upgrades. See #1433 + if (db_upgrade($dbversion, '', $quiet)) { + system_message(elgg_echo('upgrade:db')); + } + + if (upgrade_code($dbversion, $quiet)) { + system_message(elgg_echo('upgrade:core')); + + // Now we trigger an event to give the option for plugins to do something + $upgrade_details = new stdClass; + $upgrade_details->from = $dbversion; + $upgrade_details->to = get_version(); + + elgg_trigger_event('upgrade', 'upgrade', $upgrade_details); + + return true; + } + + return false; +} + +/** + * Boot straps into 1.8 upgrade system from 1.7 + * + * This runs all the 1.7 upgrades, then sets the processed_upgrades to all existing 1.7 upgrades. + * Control is then passed back to the main upgrade function which detects and runs the + * 1.8 upgrades, regardless of filename convention. + * + * @return bool + * @access private + */ +function elgg_upgrade_bootstrap_17_to_18() { + $db_version = (int) datalist_get('version'); + + // the 1.8 upgrades before the upgrade system change that are interspersed with 1.7 upgrades. + $upgrades_18 = array( + '2010111501.php', + '2010121601.php', + '2010121602.php', + '2010121701.php', + '2010123101.php', + '2011010101.php', + ); + + $upgrade_files = elgg_get_upgrade_files(); + $processed_upgrades = array(); + + foreach ($upgrade_files as $upgrade_file) { + // ignore if not in 1.7 format or if it's a 1.8 upgrade + if (in_array($upgrade_file, $upgrades_18) || !preg_match("/[0-9]{10}\.php/", $upgrade_file)) { + continue; + } + + $upgrade_version = elgg_get_upgrade_file_version($upgrade_file); + + // this has already been run in a previous 1.7.X -> 1.7.X upgrade + if ($upgrade_version < $db_version) { + $processed_upgrades[] = $upgrade_file; + } + } + + return elgg_set_processed_upgrades($processed_upgrades); +} + +/** + * Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades. + * + * @see _elgg_upgrade_lock() + * + * @return bool + * @access private + */ +function _elgg_upgrade_lock() { + global $CONFIG; + + if (!_elgg_upgrade_is_locked()) { + // lock it + insert_data("create table {$CONFIG->dbprefix}upgrade_lock (id INT)"); + elgg_log('Locked for upgrade.', 'NOTICE'); + return true; + } + + elgg_log('Cannot lock for upgrade: already locked.', 'WARNING'); + return false; +} + +/** + * Unlocks upgrade. + * + * @see _elgg_upgrade_lock() + * + * @access private + */ +function _elgg_upgrade_unlock() { + global $CONFIG; + delete_data("drop table {$CONFIG->dbprefix}upgrade_lock"); + elgg_log('Upgrade unlocked.', 'NOTICE'); +} + +/** + * Checks if upgrade is locked + * + * @return bool + * @access private + */ +function _elgg_upgrade_is_locked() { + global $CONFIG; + + $is_locked = count(get_data("show tables like '{$CONFIG->dbprefix}upgrade_lock'")); + + // @todo why? + _elgg_invalidate_query_cache(); + + return $is_locked; +} diff --git a/engine/lib/upgrades/2008100701.php b/engine/lib/upgrades/2008100701.php index cf976e84c..b8d4dfdbc 100644 --- a/engine/lib/upgrades/2008100701.php +++ b/engine/lib/upgrades/2008100701.php @@ -1,8 +1,7 @@ <?php - /// Activate mail plugin - /** - * Because Elgg now has a plugable account activation process we need to activate - * the email account activation plugin for existing installs. - */ - enable_plugin('uservalidationbyemail', $CONFIG->site->guid); -?>
\ No newline at end of file + +/** + * Because Elgg now has a plugable account activation process we need to activate + * the email account activation plugin for existing installs. + */ +enable_plugin('uservalidationbyemail', $CONFIG->site->guid); diff --git a/engine/lib/upgrades/2008101303.php b/engine/lib/upgrades/2008101303.php index c98eace74..69e44e3a0 100644 --- a/engine/lib/upgrades/2008101303.php +++ b/engine/lib/upgrades/2008101303.php @@ -1,11 +1,9 @@ <?php - // Upgrade to solve login issue - - if ($users = get_entities_from_metadata('validated_email', '', 'user', '', 0,9999)) { - foreach($users as $user) { - set_user_validation_status($user->guid, true, 'email'); - } - } - -?>
\ No newline at end of file +// Upgrade to solve login issue + +if ($users = get_entities_from_metadata('validated_email', '', 'user', '', 0, 9999)) { + foreach ($users as $user) { + set_user_validation_status($user->guid, true, 'email'); + } +} diff --git a/engine/lib/upgrades/2009022701.php b/engine/lib/upgrades/2009022701.php index 92d540cd3..54083a34d 100644 --- a/engine/lib/upgrades/2009022701.php +++ b/engine/lib/upgrades/2009022701.php @@ -1,8 +1,7 @@ <?php - global $CONFIG; - - /** - * Disable update client since this has now been removed. - */ - disable_plugin('updateclient', $CONFIG->site->guid); -?>
\ No newline at end of file +global $CONFIG; + +/** + * Disable update client since this has now been removed. + */ +disable_plugin('updateclient', $CONFIG->site->guid); diff --git a/engine/lib/upgrades/2009041701.php b/engine/lib/upgrades/2009041701.php index 609c7e569..7b31a3bc9 100644 --- a/engine/lib/upgrades/2009041701.php +++ b/engine/lib/upgrades/2009041701.php @@ -1,10 +1,8 @@ <?php - global $CONFIG; - - /// Activate kses - /** - * Elgg now has kses tag filtering built as a plugin. This needs to be enabled. - */ - enable_plugin('kses', $CONFIG->site->guid); -?>
\ No newline at end of file +global $CONFIG; + +/** + * Elgg now has kses tag filtering built as a plugin. This needs to be enabled. + */ +enable_plugin('kses', $CONFIG->site->guid); diff --git a/engine/lib/upgrades/2009070101.php b/engine/lib/upgrades/2009070101.php index edd8ce4b4..d0eae9b91 100644 --- a/engine/lib/upgrades/2009070101.php +++ b/engine/lib/upgrades/2009070101.php @@ -1,11 +1,9 @@ <?php - global $CONFIG; - - /// Deprecate kses and activate htmlawed - /** - * Kses appears to be a dead project so we are deprecating it in favour of htmlawed. - */ - disable_plugin('kses', $CONFIG->site->guid); - enable_plugin('htmlawed', $CONFIG->site->guid); -?>
\ No newline at end of file +global $CONFIG; + +/** + * Kses appears to be a dead project so we are deprecating it in favour of htmlawed. + */ +disable_plugin('kses', $CONFIG->site->guid); +enable_plugin('htmlawed', $CONFIG->site->guid); diff --git a/engine/lib/upgrades/2009102801.php b/engine/lib/upgrades/2009102801.php index b72e0a781..3ad113fb2 100644 --- a/engine/lib/upgrades/2009102801.php +++ b/engine/lib/upgrades/2009102801.php @@ -1,154 +1,180 @@ <?php -// disable timeout for large sites. -set_time_limit(0); +/** + * Move user's data directories from using username to registration date + */ /** - Elgg 1.0 + * Generates a file matrix like Elgg 1.0 did + * + * @param string $username Username of user + * + * @return string File matrix path */ -function file_matrix_1_0($username) -{ +function file_matrix_1_0($username) { $matrix = ""; - + $len = strlen($username); - if ($len > 5) + if ($len > 5) { $len = 5; - + } + for ($n = 0; $n < $len; $n++) { - if (ctype_alnum($username[$n])) + if (ctype_alnum($username[$n])) { $matrix .= $username[$n] . "/"; - } + } + } - return $matrix.$username."/"; + return $matrix . $username . "/"; } /** - Elgg 1.1, 1.2 and 1.5 + * Generate a file matrix like Elgg 1.1, 1.2 and 1.5 + * + * @param string $filename The filename + * + * @return string */ -function file_matrix_1_1($filename) -{ +function file_matrix_1_1($filename) { $matrix = ""; - + $name = $filename; $filename = mb_str_split($filename); - if (!$filename) return false; - + if (!$filename) { + return false; + } + $len = count($filename); - if ($len > 5) + if ($len > 5) { $len = 5; - + } + for ($n = 0; $n < $len; $n++) { $matrix .= $filename[$n] . "/"; - } + } - return $matrix.$name."/"; + return $matrix . $name . "/"; } -function mb_str_split($string, $charset = 'UTF8') -{ - if (is_callable('mb_substr')) - { +/** + * Handle splitting multibyte strings + * + * @param string $string String to split. + * @param string $charset Charset to use. + * + * @return array|false + */ +function mb_str_split($string, $charset = 'UTF8') { + if (is_callable('mb_substr')) { $length = mb_strlen($string); $array = array(); - - while ($length) - { + + while ($length) { $array[] = mb_substr($string, 0, 1, $charset); $string = mb_substr($string, 1, $length, $charset); - + $length = mb_strlen($string); } - + return $array; - } - else + } else { return str_split($string); - + } + return false; } /** - Elgg 1.6 + * 1.6 style file matrix + * + * @param string $filename The filename + * + * @return string */ -function file_matrix_1_6($filename) -{ +function file_matrix_1_6($filename) { $invalid_fs_chars = '*\'\\/"!$%^&*.%(){}[]#~?<>;|¬`@-+='; - + $matrix = ""; - + $name = $filename; $filename = mb_str_split($filename); - if (!$filename) return false; - + if (!$filename) { + return false; + } + $len = count($filename); - if ($len > 5) + if ($len > 5) { $len = 5; - + } + for ($n = 0; $n < $len; $n++) { - + // Prevent a matrix being formed with unsafe characters $char = $filename[$n]; - if (strpos($invalid_fs_chars, $char)!==false) + if (strpos($invalid_fs_chars, $char) !== false) { $char = '_'; - + } + $matrix .= $char . "/"; - } + } - return $matrix.$name."/"; + return $matrix . $name . "/"; } /** - * Scans a directory and moves any files from $from to $to + * Scans a directory and moves any files from $from to $to * preserving structure and handling existing paths. * Will no overwrite files in $to. * * TRAILING SLASHES REQUIRED. * - * @param $from From dir. - * @param $to To dir. - * @param $move Bool. True to move, false to copy. - * @param $preference str to|from If file collisions, which dir has preference. + * @param string $from From dir. + * @param string $to To dir. + * @param bool $move True to move, false to copy. + * @param string $preference to|from If file collisions, which dir has preference. + * + * @return bool */ -function merge_directories($from, $to, $move=false, $preference='to') { +function merge_directories($from, $to, $move = false, $preference = 'to') { if (!$entries = scandir($from)) { return false; } - + // character filtering needs to be elsewhere. if (!is_dir($to)) { mkdir($to, 0700, true); } - + if ($move === true) { $f = 'rename'; } else { $f = 'copy'; } - + foreach ($entries as $entry) { if ($entry == '.' || $entry == '..') { continue; } - + $from_path = $from . $entry; $to_path = $to . $entry; - + // check to see if the path exists and is a dir, if so, recurse. if (is_dir($from_path) && is_dir($to_path)) { $from_path .= '/'; $to_path .= '/'; merge_directories($from_path, $to_path, $move, $preference); - + // since it's a dir that already exists we don't need to move it continue; } - + // only move if target doesn't exist or if preference is for the from dir if (!file_exists($to_path) || $preference == 'from') { - + if ($f($from_path, $to_path)) { //elgg_dump("Moved/Copied $from_path to $to_path"); } @@ -158,36 +184,39 @@ function merge_directories($from, $to, $move=false, $preference='to') { } } +/** + * Create a 1.7 style user file matrix based upon date. + * + * @param int $guid Guid of owner + * + * @return string File matrix path + */ function user_file_matrix($guid) { // lookup the entity $user = get_entity($guid); - if ($user->type != 'user') - { + if ($user->type != 'user') { // only to be used for user directories return FALSE; } - - if (!$user->time_created) { - // fall back to deprecated method - return $this->deprecated_file_matrix($user->username); - } - + $time_created = date('Y/m/d', $user->time_created); return "$time_created/$user->guid/"; } -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE; +global $ENTITY_CACHE, $CONFIG; /** - Upgrade file locations + * Upgrade file locations */ -$users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity WHERE username != ''"); +$users = mysql_query("SELECT guid, username + FROM {$CONFIG->dbprefix}users_entity WHERE username != ''"); while ($user = mysql_fetch_object($users)) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); $to = $CONFIG->dataroot . user_file_matrix($user->guid); foreach (array('1_0', '1_1', '1_6') as $version) { $function = "file_matrix_$version"; $from = $CONFIG->dataroot . $function($user->username); - merge_directories($from, $to, $move=TRUE, $preference='from'); + merge_directories($from, $to, $move = TRUE, $preference = 'from'); } } diff --git a/engine/lib/upgrades/2010033101.php b/engine/lib/upgrades/2010033101.php new file mode 100644 index 000000000..4779295fd --- /dev/null +++ b/engine/lib/upgrades/2010033101.php @@ -0,0 +1,70 @@ +<?php + +/** + * Conditional upgrade for UTF8 as described in https://github.com/elgg/elgg/issues/1928 + */ + +// get_version() returns the code version. +// we want the DB version. +$dbversion = (int) datalist_get('version'); + +// 2009100701 was the utf8 upgrade for 1.7. +// if we've already upgraded, don't try again. +if ($dbversion < 2009100701) { + // if the default client connection is utf8 there is no reason + // to run this upgrade because the strings are already stored correctly. + + // start a new link to the DB to see what its defaults are. + $link = mysql_connect($CONFIG->dbhost, $CONFIG->dbuser, $CONFIG->dbpass, TRUE); + mysql_select_db($CONFIG->dbname, $link); + + $q = "SHOW VARIABLES LIKE 'character_set_client'"; + $r = mysql_query($q); + $client = mysql_fetch_assoc($r); + + $q = "SHOW VARIABLES LIKE 'character_set_connection'"; + $r = mysql_query($q); + $connection = mysql_fetch_assoc($r); + + // only run upgrade if not already talking utf8. + if ($client['Value'] != 'utf8' && $connection['Value'] != 'utf8') { + $qs = array(); + $qs[] = "SET NAMES utf8"; + + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}metastrings DISABLE KEYS"; + $qs[] = "REPLACE INTO {$CONFIG->dbprefix}metastrings (id, string) + SELECT id, unhex(hex(convert(string using latin1))) + FROM {$CONFIG->dbprefix}metastrings"; + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}metastrings ENABLE KEYS"; + + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}groups_entity DISABLE KEYS"; + $qs[] = "REPLACE INTO {$CONFIG->dbprefix}groups_entity (guid, name, description) + SELECT guid, unhex(hex(convert(name using latin1))), + unhex(hex(convert(description using latin1))) + FROM {$CONFIG->dbprefix}groups_entity"; + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}groups_entity ENABLE KEYS"; + + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}objects_entity DISABLE KEYS"; + $qs[] = "REPLACE INTO {$CONFIG->dbprefix}objects_entity (guid, title, description) + SELECT guid, unhex(hex(convert(title using latin1))), + unhex(hex(convert(description using latin1))) + FROM {$CONFIG->dbprefix}objects_entity"; + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}objects_entity ENABLE KEYS"; + + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity DISABLE KEYS"; + $qs[] = "REPLACE INTO {$CONFIG->dbprefix}users_entity + (guid, name, username, password, salt, email, language, code, + banned, last_action, prev_last_action, last_login, prev_last_login) + SELECT guid, unhex(hex(convert(name using latin1))), + username, password, salt, email, language, code, + banned, last_action, prev_last_action, last_login, prev_last_login + FROM {$CONFIG->dbprefix}users_entity"; + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity ENABLE KEYS"; + + foreach ($qs as $q) { + if (!update_data($q)) { + throw new Exception('Couldn\'t execute upgrade query: ' . $q); + } + } + } +} diff --git a/engine/lib/upgrades/2010040201.php b/engine/lib/upgrades/2010040201.php new file mode 100644 index 000000000..789bf5dfc --- /dev/null +++ b/engine/lib/upgrades/2010040201.php @@ -0,0 +1,41 @@ +<?php + +/** + * Pull admin metadata setting into users_entity table column + */ + +$siteadmin = get_metastring_id('siteadmin'); +$admin = get_metastring_id('admin'); +$yes = get_metastring_id('yes'); +$one = get_metastring_id('1'); + +$qs = array(); + +$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity DISABLE KEYS"; + +$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity + ADD admin ENUM('yes', 'no') NOT NULL DEFAULT 'no' AFTER `banned`"; + +$qs[] = "UPDATE {$CONFIG->dbprefix}users_entity SET admin = 'yes' where guid IN (select x.guid FROM( +SELECT * FROM {$CONFIG->dbprefix}users_entity as e, + {$CONFIG->dbprefix}metadata as md + WHERE ( + md.name_id IN ('$admin', '$siteadmin') + AND md.value_id IN ('$yes', '$one') + AND e.guid = md.entity_guid + AND e.banned = 'no' + )) as x)"; + +$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity ADD KEY admin (admin)"; + +$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity ENABLE KEYS"; + +$qs[] = "DELETE FROM {$CONFIG->dbprefix}metadata + WHERE ( + name_id IN ('$admin', '$siteadmin') + AND value_id IN ('$yes', '$one') + )"; + +foreach ($qs as $q) { + update_data($q); +} diff --git a/engine/lib/upgrades/2010052601.php b/engine/lib/upgrades/2010052601.php new file mode 100644 index 000000000..a9cca6dc5 --- /dev/null +++ b/engine/lib/upgrades/2010052601.php @@ -0,0 +1,27 @@ +<?php + +// Upgrade to fix encoding issues on group data: #1963 + +elgg_set_ignore_access(TRUE); + +$params = array('type' => 'group', + 'limit' => 0); +$groups = elgg_get_entities($params); +if ($groups) { + foreach ($groups as $group) { + $group->name = _elgg_html_decode($group->name); + $group->description = _elgg_html_decode($group->description); + $group->briefdescription = _elgg_html_decode($group->briefdescription); + $group->website = _elgg_html_decode($group->website); + if ($group->interests) { + $tags = $group->interests; + foreach ($tags as $index => $tag) { + $tags[$index] = _elgg_html_decode($tag); + } + $group->interests = $tags; + } + + $group->save(); + } +} +elgg_set_ignore_access(FALSE); diff --git a/engine/lib/upgrades/2010060101.php b/engine/lib/upgrades/2010060101.php new file mode 100644 index 000000000..bb7f7c1a6 --- /dev/null +++ b/engine/lib/upgrades/2010060101.php @@ -0,0 +1,16 @@ +<?php + +/** + * Clears old simplecache variables out of database + */ + +$query = "DELETE FROM {$CONFIG->dbprefix}datalists WHERE name LIKE 'simplecache%'"; + +delete_data($query); + +if ($CONFIG->simplecache_enabled) { + datalist_set('simplecache_enabled', 1); + elgg_regenerate_simplecache(); +} else { + datalist_set('simplecache_enabled', 0); +} diff --git a/engine/lib/upgrades/2010060401.php b/engine/lib/upgrades/2010060401.php new file mode 100644 index 000000000..6d628b8eb --- /dev/null +++ b/engine/lib/upgrades/2010060401.php @@ -0,0 +1,59 @@ +<?php + +/** + * Get each user's notify* relationships and confirm that they have a friend + * or member relationship depending on type. This fixes the notify relationships + * that were not updated to due to #1837 + */ + +$count = 0; + +$user_guids = mysql_query("SELECT guid FROM {$CONFIG->dbprefix}users_entity"); +while ($user = mysql_fetch_object($user_guids)) { + + $query = "SELECT * FROM {$CONFIG->dbprefix}entity_relationships + WHERE guid_one=$user->guid AND relationship LIKE 'notify%'"; + $relationships = mysql_query($query); + if (mysql_num_rows($relationships) == 0) { + // no notify relationships for this user + continue; + } + + while ($obj = mysql_fetch_object($relationships)) { + $query = "SELECT type FROM {$CONFIG->dbprefix}entities WHERE guid=$obj->guid_two"; + $results = mysql_query($query); + if (mysql_num_rows($results) == 0) { + // entity doesn't exist - shouldn't be possible + continue; + } + + $entity = mysql_fetch_object($results); + + switch ($entity->type) { + case 'user': + $relationship_type = 'friend'; + break; + case 'group': + $relationship_type = 'member'; + break; + } + + if (isset($relationship_type)) { + $query = "SELECT * FROM {$CONFIG->dbprefix}entity_relationships + WHERE guid_one=$user->guid AND relationship='$relationship_type' + AND guid_two=$obj->guid_two"; + $results = mysql_query($query); + + if (mysql_num_rows($results) == 0) { + $query = "DELETE FROM {$CONFIG->dbprefix}entity_relationships WHERE id=$obj->id"; + mysql_query($query); + $count++; + } + } + } + +} + +if (is_callable('error_log')) { + error_log("Deleted $count notify relationships in upgrade"); +} diff --git a/engine/lib/upgrades/2010061501.php b/engine/lib/upgrades/2010061501.php new file mode 100644 index 000000000..744c28fd5 --- /dev/null +++ b/engine/lib/upgrades/2010061501.php @@ -0,0 +1,75 @@ +<?php +/** + * utf8 database conversion and file merging for usernames with multibyte chars + * + */ + + +// check that we need to do the utf8 conversion +// C&P logic from 2010033101 +$dbversion = (int) datalist_get('version'); + +if ($dbversion < 2009100701) { + // start a new link to the DB to see what its defaults are. + $link = mysql_connect($CONFIG->dbhost, $CONFIG->dbuser, $CONFIG->dbpass, TRUE); + mysql_select_db($CONFIG->dbname, $link); + + $q = "SHOW VARIABLES LIKE 'character_set_client'"; + $r = mysql_query($q); + $client = mysql_fetch_assoc($r); + + $q = "SHOW VARIABLES LIKE 'character_set_connection'"; + $r = mysql_query($q); + $connection = mysql_fetch_assoc($r); + + // only run upgrade if not already talking utf8 + if ($client['Value'] != 'utf8' && $connection['Value'] != 'utf8') { + $qs = array(); + $qs[] = "SET NAMES utf8"; + + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity DISABLE KEYS"; + $qs[] = "REPLACE INTO {$CONFIG->dbprefix}users_entity + (guid, name, username, password, salt, email, language, code, + banned, admin, last_action, prev_last_action, last_login, prev_last_login) + + SELECT guid, name, unhex(hex(convert(username using latin1))), + password, salt, email, language, code, + banned, admin, last_action, prev_last_action, last_login, prev_last_login + FROM {$CONFIG->dbprefix}users_entity"; + + $qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity ENABLE KEYS"; + + foreach ($qs as $q) { + if (!update_data($q)) { + throw new Exception('Couldn\'t execute upgrade query: ' . $q); + } + } + + global $ENTITY_CACHE; + + /** + Upgrade file locations + */ + // new connection to force into utf8 mode to get the old name + $link = mysql_connect($CONFIG->dbhost, $CONFIG->dbuser, $CONFIG->dbpass, TRUE); + mysql_select_db($CONFIG->dbname, $link); + + // must be the first command + mysql_query("SET NAMES utf8"); + + $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity + WHERE username != ''", $link); + while ($user = mysql_fetch_object($users)) { + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); + + + $to = $CONFIG->dataroot . user_file_matrix($user->guid); + foreach (array('1_0', '1_1', '1_6') as $version) { + $function = "file_matrix_$version"; + $from = $CONFIG->dataroot . $function($user->username); + merge_directories($from, $to, $move = TRUE, $preference = 'from'); + } + } + } +} diff --git a/engine/lib/upgrades/2010062301.php b/engine/lib/upgrades/2010062301.php new file mode 100644 index 000000000..f679fa46d --- /dev/null +++ b/engine/lib/upgrades/2010062301.php @@ -0,0 +1,33 @@ +<?php + +/** + * Change ownership of group ACLs to group entity + */ + +elgg_set_ignore_access(TRUE); + +$params = array('type' => 'group', + 'limit' => 0); +$groups = elgg_get_entities($params); +if ($groups) { + foreach ($groups as $group) { + $acl = $group->group_acl; + + try { + $query = "UPDATE {$CONFIG->dbprefix}access_collections + SET owner_guid = $group->guid WHERE id = $acl"; + update_data($query); + } catch (Exception $e) { + // no acl so create one + $ac_name = elgg_echo('groups:group') . ": " . $group->name; + $group_acl = create_access_collection($ac_name, $group->guid); + if ($group_acl) { + create_metadata($group->guid, 'group_acl', $group_acl, 'integer', $group->owner_guid); + $object->group_acl = $group_id; + } + } + + } +} +elgg_set_ignore_access(FALSE); + diff --git a/engine/lib/upgrades/2010062302.php b/engine/lib/upgrades/2010062302.php new file mode 100644 index 000000000..fe33e12ea --- /dev/null +++ b/engine/lib/upgrades/2010062302.php @@ -0,0 +1,33 @@ +<?php + +/** + * Make sure that everyone who belongs to a group is a member of the group's access collection + */ + + +elgg_set_ignore_access(TRUE); + +$params = array('type' => 'group', 'limit' => 0); +$groups = elgg_get_entities($params); +if ($groups) { + foreach ($groups as $group) { + $acl = $group->group_acl; + + $query = "SELECT u.guid FROM {$CONFIG->dbprefix}users_entity u + JOIN {$CONFIG->dbprefix}entity_relationships r + ON u.guid = r.guid_one AND r.relationship = 'member' AND r.guid_two = $group->guid + LEFT JOIN {$CONFIG->dbprefix}access_collection_membership a + ON u.guid = a.user_guid AND a.access_collection_id = $acl + WHERE a.user_guid IS NULL"; + + $results = get_data($query); + if ($results != FALSE) { + foreach ($results as $user) { + $insert = "INSERT INTO {$CONFIG->dbprefix}access_collection_membership + (user_guid, access_collection_id) VALUES ($user->guid, $acl)"; + insert_data($insert); + } + } + } +} +elgg_set_ignore_access(FALSE); diff --git a/engine/lib/upgrades/2010070301.php b/engine/lib/upgrades/2010070301.php new file mode 100644 index 000000000..af5c80419 --- /dev/null +++ b/engine/lib/upgrades/2010070301.php @@ -0,0 +1,9 @@ +<?php + +/** + * Group join river view has been renamed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river SET view='river/relationship/member/create' + WHERE view='river/group/create' AND action_type='join'"; +update_data($query); diff --git a/engine/lib/upgrades/2010071001.php b/engine/lib/upgrades/2010071001.php new file mode 100644 index 000000000..5594493a8 --- /dev/null +++ b/engine/lib/upgrades/2010071001.php @@ -0,0 +1,58 @@ +<?php +/** + * Change profile image names to use guid rather than username + */ + +/** + * Need the same function to generate a user matrix, but can't call it + * the same thing as the previous update. + * + * @param int $guid User guid. + * + * @return string File matrix + */ +function user_file_matrix_2010071001($guid) { + // lookup the entity + $user = get_entity($guid); + if ($user->type != 'user') { + // only to be used for user directories + return FALSE; + } + + if (!$user->time_created) { + // no idea where this user has its files + return FALSE; + } + + $time_created = date('Y/m/d', $user->time_created); + return "$time_created/$user->guid/"; +} + +$sizes = array('large', 'medium', 'small', 'tiny', 'master', 'topbar'); + +global $ENTITY_CACHE, $CONFIG; +$users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity + WHERE username != ''"); +while ($user = mysql_fetch_object($users)) { + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); + + $user_directory = user_file_matrix_2010071001($user->guid); + if (!$user_directory) { + continue; + } + $profile_directory = $CONFIG->dataroot . $user_directory . "profile/"; + if (!file_exists($profile_directory)) { + continue; + } + + foreach ($sizes as $size) { + $old_filename = "$profile_directory{$user->username}{$size}.jpg"; + $new_filename = "$profile_directory{$user->guid}{$size}.jpg"; + if (file_exists($old_filename)) { + if (!rename($old_filename, $new_filename)) { + error_log("Failed to rename profile photo for $user->username"); + } + } + } +} diff --git a/engine/lib/upgrades/2010071002.php b/engine/lib/upgrades/2010071002.php new file mode 100644 index 000000000..52aa15ef5 --- /dev/null +++ b/engine/lib/upgrades/2010071002.php @@ -0,0 +1,50 @@ +<?php +/** + * Update the notifications based on all friends and access collections + */ + +// loop through all users checking collections and notifications +global $ENTITY_CACHE, $CONFIG; +global $NOTIFICATION_HANDLERS; +$users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity + WHERE username != ''"); +while ($user = mysql_fetch_object($users)) { + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); + + $user = get_entity($user->guid); + foreach ($NOTIFICATION_HANDLERS as $method => $foo) { + $notify = "notify$method"; + $metaname = "collections_notifications_preferences_$method"; + $collections_preferences = $user->$metaname; + if (!$collections_preferences) { + continue; + } + if (!is_array($collections_preferences)) { + $collections_preferences = array($collections_preferences); + } + foreach ($collections_preferences as $collection_id) { + // check the all friends notifications + if ($collection_id == -1) { + $options = array( + 'relationship' => 'friend', + 'relationship_guid' => $user->guid, + 'limit' => 0 + ); + $friends = elgg_get_entities_from_relationship($options); + foreach ($friends as $friend) { + if (!check_entity_relationship($user->guid, $notify, $friend->guid)) { + add_entity_relationship($user->guid, $notify, $friend->guid); + } + } + } else { + $members = get_members_of_access_collection($collection_id, TRUE); + foreach ($members as $member) { + if (!check_entity_relationship($user->guid, $notify, $members)) { + add_entity_relationship($user->guid, $notify, $member); + } + } + } + } + } +} diff --git a/engine/lib/upgrades/2010111501.php b/engine/lib/upgrades/2010111501.php new file mode 100644 index 000000000..15e4a7d35 --- /dev/null +++ b/engine/lib/upgrades/2010111501.php @@ -0,0 +1,33 @@ +<?php +/** + * Set validation metadata on unvalidated users to false rather than + * not existing. This is needed because of the change in how validation is + * being handled. + */ + +// turn off system log because of all the metadata this can create +elgg_unregister_event_handler('all', 'all', 'system_log_listener'); +elgg_unregister_event_handler('log', 'systemlog', 'system_log_default_logger'); + +$ia = elgg_set_ignore_access(TRUE); +$hidden_entities = access_get_show_hidden_status(); +access_show_hidden_entities(TRUE); + +$validated_id = get_metastring_id('validated'); +$one_id = get_metastring_id(1); + +$query = "SELECT guid FROM {$CONFIG->dbprefix}entities e + WHERE e.type = 'user' AND e.enabled = 'no' AND + NOT EXISTS ( + SELECT 1 FROM {$CONFIG->dbprefix}metadata md + WHERE md.entity_guid = e.guid + AND md.name_id = $validated_id + AND md.value_id = $one_id)"; + +$user_guids = mysql_query($query); +while ($user_guid = mysql_fetch_object($user_guids)) { + create_metadata($user_guid->guid, 'validated', false, '', 0, ACCESS_PUBLIC, false); +} + +access_show_hidden_entities($hidden_entities); +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2010121601.php b/engine/lib/upgrades/2010121601.php new file mode 100644 index 000000000..ad7d26adb --- /dev/null +++ b/engine/lib/upgrades/2010121601.php @@ -0,0 +1,9 @@ +<?php +/** + * Create friends river view has been changed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river + SET view='river/relationship/friend/create', action_type='create' + WHERE view='friends/river/create' AND action_type='friend'"; +update_data($query); diff --git a/engine/lib/upgrades/2010121602.php b/engine/lib/upgrades/2010121602.php new file mode 100644 index 000000000..5b0996b5e --- /dev/null +++ b/engine/lib/upgrades/2010121602.php @@ -0,0 +1,10 @@ +<?php +/** + * Create comment river view has been changed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river + SET view='river/annotation/generic_comment/create' + WHERE view='annotation/annotate' AND action_type='comment'"; +update_data($query); + diff --git a/engine/lib/upgrades/2010121701.php b/engine/lib/upgrades/2010121701.php new file mode 100644 index 000000000..375654bac --- /dev/null +++ b/engine/lib/upgrades/2010121701.php @@ -0,0 +1,10 @@ +<?php +/** + * Create group forum topic river view has been changed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river + SET view='river/object/groupforumtopic/create' + WHERE view='river/forum/topic/create' AND action_type='create'"; +update_data($query); + diff --git a/engine/lib/upgrades/2010123101.php b/engine/lib/upgrades/2010123101.php new file mode 100644 index 000000000..f4befd1a8 --- /dev/null +++ b/engine/lib/upgrades/2010123101.php @@ -0,0 +1,9 @@ +<?php +/** + * Set default access for older sites + */ + +$access = elgg_get_config('default_access'); +if ($access == false) { + elgg_save_config('default_access', ACCESS_LOGGED_IN); +} diff --git a/engine/lib/upgrades/2011010101.php b/engine/lib/upgrades/2011010101.php new file mode 100644 index 000000000..f4411ee20 --- /dev/null +++ b/engine/lib/upgrades/2011010101.php @@ -0,0 +1,98 @@ +<?php +/** + * Migrate plugins to the new system using ElggPlugin and private settings + */ + +$old_ia = elgg_set_ignore_access(true); + +$site = get_config('site'); +$old_plugin_order = unserialize($site->pluginorder); +$old_enabled_plugins = $site->enabled_plugins; + +$db_prefix = get_config('dbprefix'); +$plugin_subtype_id = get_subtype_id('object', 'plugin'); + +// easy one first: make sure the the site owns all plugin entities. +$q = "UPDATE {$db_prefix}entities e + SET owner_guid = $site->guid, container_guid = $site->guid + WHERE e.type = 'object' AND e.subtype = $plugin_subtype_id"; + +$r = update_data($q); + +// rewrite all plugin:setting:* to ELGG_PLUGIN_USER_SETTING_PREFIX . * +$q = "UPDATE {$db_prefix}private_settings + SET name = replace(name, 'plugin:settings:', '" . ELGG_PLUGIN_USER_SETTING_PREFIX . "') + WHERE name LIKE 'plugin:settings:%'"; + +$r = update_data($q); + +// grab current plugin GUIDs to add a temp priority +$q = "SELECT * FROM {$db_prefix}entities e + JOIN {$db_prefix}objects_entity oe ON e.guid = oe.guid + WHERE e.type = 'object' AND e.subtype = $plugin_subtype_id"; + +$plugins = get_data($q); + +foreach ($plugins as $plugin) { + $priority = elgg_namespace_plugin_private_setting('internal', 'priority'); + set_private_setting($plugin->guid, $priority, 0); +} + +// force regenerating plugin entities +elgg_generate_plugin_entities(); + +// set the priorities for all plugins +// this function rewrites it to a normal index so use the current one. +elgg_set_plugin_priorities($old_plugin_order); + +// add relationships for enabled plugins +if ($old_enabled_plugins) { + // they might only have one plugin enabled. + if (!is_array($old_enabled_plugins)) { + $old_enabled_plugins = array($old_enabled_plugins); + } + + // sometimes there were problems and you'd get 1000s of enabled plugins. + $old_enabled_plugins = array_unique($old_enabled_plugins); + + foreach ($old_enabled_plugins as $plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + + if ($plugin) { + $plugin->activate(); + } + } +} + +// invalidate caches +elgg_invalidate_simplecache(); +elgg_reset_system_cache(); + +// clean up. +remove_metadata($site->guid, 'pluginorder'); +remove_metadata($site->guid, 'enabled_plugins'); + +elgg_set_ignore_access($old_id); + +/** + * @hack + * + * We stop the upgrade at this point because plugins weren't given the chance to + * load due to the new plugin code introduced with Elgg 1.8. Instead, we manually + * set the version and start the upgrade process again. + * + * The variables from upgrade_code() are available because this script was included + */ +if ($upgrade_version > $version) { + datalist_set('version', $upgrade_version); +} + +// add ourselves to the processed_upgrades. +$processed_upgrades[] = '2011010101.php'; + +$processed_upgrades = array_unique($processed_upgrades); +elgg_set_processed_upgrades($processed_upgrades); + +_elgg_upgrade_unlock(); + +forward('upgrade.php'); diff --git a/engine/lib/upgrades/2011021800-1.8_svn-goodbye_walled_garden-083121a656d06894.php b/engine/lib/upgrades/2011021800-1.8_svn-goodbye_walled_garden-083121a656d06894.php new file mode 100644 index 000000000..40b2c71d5 --- /dev/null +++ b/engine/lib/upgrades/2011021800-1.8_svn-goodbye_walled_garden-083121a656d06894.php @@ -0,0 +1,34 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011021800 + * goodbye_walled_garden + * + * Removes the Walled Garden plugin in favor of new system settings + */ + +global $CONFIG; + +$access = elgg_set_ignore_access(TRUE); + +if (elgg_is_active_plugin('walledgarden')) { + disable_plugin('walledgarden'); + set_config('allow_registration', FALSE); + set_config('walled_garden', TRUE); +} else { + set_config('allow_registration', TRUE); + set_config('walled_garden', FALSE); +} + +// this was for people who manually set the config option +$disable_registration = elgg_get_config('disable_registration'); +if ($disable_registration !== null) { + $allow_registration = !$disable_registration; + elgg_save_config('allow_registration', $allow_registration); + + $site = elgg_get_site_entity(); + $query = "DELETE FROM {$CONFIG->dbprefix}config + WHERE name = 'disable_registration' AND site_guid = $site->guid"; + delete_data($query); +} + +elgg_set_ignore_access($access); diff --git a/engine/lib/upgrades/2011022000-1.8_svn-custom_profile_fields-390ac967b0bb5665.php b/engine/lib/upgrades/2011022000-1.8_svn-custom_profile_fields-390ac967b0bb5665.php new file mode 100644 index 000000000..7561b84ba --- /dev/null +++ b/engine/lib/upgrades/2011022000-1.8_svn-custom_profile_fields-390ac967b0bb5665.php @@ -0,0 +1,59 @@ +<?php +/** + * Elgg 2011010401 upgrade 00 + * custom_profile_fields + * + * Migrate 1.7 style custom profile fields to 1.8 + */ + +$plugin = elgg_get_plugin_from_id('profile'); + +// plugin not installed +if (!$plugin) { + return true; +} + +$settings = $plugin->getAllSettings(); +// no fields to migrate +if (!$settings['user_defined_fields']) { + return true; +} + +$order = array(); +$remove_settings = array(); + +// make sure we have a name and type +foreach ($settings as $k => $v) { + if (!preg_match('/admin_defined_profile_([0-9]+)/i', $k, $matches)) { + continue; + } + + $i = $matches[1]; + $type_name = "admin_defined_profile_type_$i"; + $type = elgg_extract($type_name, $settings, null); + + if ($type) { + // field name + elgg_save_config($k, $v); + // field value + elgg_save_config($type_name, $type); + + $order[] = $i; + $remove_settings[] = $k; + $remove_settings[] = $type_name; + } +} + +if ($order) { + // these will always need to be in order, but there might be gaps + ksort($order); + + $order_str = implode(',', $order); + elgg_save_config('profile_custom_fields', $order_str); + + foreach ($remove_settings as $name) { + $plugin->unsetSetting($name); + } + + $plugin->unsetSetting('user_defined_fields'); +}
\ No newline at end of file diff --git a/engine/lib/upgrades/2011030700-1.8_svn-blog_status_metadata-4645225d7b440876.php b/engine/lib/upgrades/2011030700-1.8_svn-blog_status_metadata-4645225d7b440876.php new file mode 100644 index 000000000..fe2af9928 --- /dev/null +++ b/engine/lib/upgrades/2011030700-1.8_svn-blog_status_metadata-4645225d7b440876.php @@ -0,0 +1,24 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011030700 + * blog_status_metadata + * + * Add a "status" metadata entry to every blog entity because in 1.8 you can have status = draft or + * status = published + */ +$ia = elgg_set_ignore_access(true); +$options = array( + 'type' => 'object', + 'subtype' => 'blog', + 'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { + if (!$entity->status) { + // create metadata owned by the original owner + create_metadata($entity->getGUID(), 'status', 'published', '', $entity->owner_guid, + $entity->access_id); + } +} +elgg_set_ignore_access($ia);
\ No newline at end of file diff --git a/engine/lib/upgrades/2011031300-1.8_svn-twitter_api-12b832a5a7a3e1bd.php b/engine/lib/upgrades/2011031300-1.8_svn-twitter_api-12b832a5a7a3e1bd.php new file mode 100644 index 000000000..df60892a6 --- /dev/null +++ b/engine/lib/upgrades/2011031300-1.8_svn-twitter_api-12b832a5a7a3e1bd.php @@ -0,0 +1,54 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011031300 + * twitter_api + * + * Updates the database for twitterservice to twitter_api changes. + */ + + +$ia = elgg_set_ignore_access(true); + +// make sure we have updated plugins +elgg_generate_plugin_entities(); + +$show_hidden = access_get_show_hidden_status(); +access_show_hidden_entities(true); + +$db_prefix = elgg_get_config('dbprefix'); +$site_guid = elgg_get_site_entity()->getGUID(); +$old = elgg_get_plugin_from_id('twitterservice'); +$new = elgg_get_plugin_from_id('twitter_api'); +$has_settings = false; + +// if not loaded, don't bother. +if (!$old || !$new) { + return true; +} + +$settings = array('consumer_key', 'consumer_secret', 'sign_on', 'new_users'); + +foreach ($settings as $setting) { + $value = $old->getSetting($setting); + if ($value) { + $has_settings = true; + $new->setSetting($setting, $value); + } +} + +// update the user settings +$q = "UPDATE {$db_prefix}private_settings + SET name = replace(name, 'twitterservice', 'twitter_api') + WHERE name like '%twitterservice%'"; + +update_data($q); + +// if there were settings, emit a notice to re-enable twitter_api +if ($has_settings) { + elgg_add_admin_notice('twitter_api:disabled', elgg_echo('update:twitter_api:deactivated')); +} + +$old->delete(); + +access_show_hidden_entities($show_hidden); +elgg_set_ignore_access($ia);
\ No newline at end of file diff --git a/engine/lib/upgrades/2011031600-1.8_svn-datalist_grows_up-0b8aec5a55cc1e1c.php b/engine/lib/upgrades/2011031600-1.8_svn-datalist_grows_up-0b8aec5a55cc1e1c.php new file mode 100644 index 000000000..379244b36 --- /dev/null +++ b/engine/lib/upgrades/2011031600-1.8_svn-datalist_grows_up-0b8aec5a55cc1e1c.php @@ -0,0 +1,18 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011031600 + * datalist_grows_up + * + * Ups the varchar to 256 for the datalist and config table. + * + * Keeping it as a varchar because of the trailing whitespace trimming it apparently does: + * http://dev.mysql.com/doc/refman/5.0/en/char.html + */ + +$db_prefix = elgg_get_config('dbprefix'); + +$q = "ALTER TABLE {$db_prefix}datalists CHANGE name name VARCHAR(255)"; +update_data($q); + +$q = "ALTER TABLE {$db_prefix}config CHANGE name name VARCHAR(255)"; +update_data($q); diff --git a/engine/lib/upgrades/2011032000-1.8_svn-widgets_arent_plugins-61836261fa280a5c.php b/engine/lib/upgrades/2011032000-1.8_svn-widgets_arent_plugins-61836261fa280a5c.php new file mode 100644 index 000000000..a20970d79 --- /dev/null +++ b/engine/lib/upgrades/2011032000-1.8_svn-widgets_arent_plugins-61836261fa280a5c.php @@ -0,0 +1,10 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011031800 + * widgets_arent_plugins + * + * At some point in Elgg's history subtype widget was registered with class ElggPlugin. + * Fix that. + */ + +update_subtype('object', 'widget', 'ElggWidget'); diff --git a/engine/lib/upgrades/2011032200-1.8_svn-admins_like_widgets-7f19d2783c1680d3.php b/engine/lib/upgrades/2011032200-1.8_svn-admins_like_widgets-7f19d2783c1680d3.php new file mode 100644 index 000000000..592adb403 --- /dev/null +++ b/engine/lib/upgrades/2011032200-1.8_svn-admins_like_widgets-7f19d2783c1680d3.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011032200 + * admins_like_widgets + * + * Give current admins widgets for those pre-1.8 + */ + +$admins = elgg_get_admins(array('limit' => 0)); +foreach ($admins as $admin) { + // call the admin handler for the make_admin event + elgg_add_admin_widgets('make_admin', 'user', $admin); +} diff --git a/engine/lib/upgrades/2011052801.php b/engine/lib/upgrades/2011052801.php new file mode 100644 index 000000000..b5a8e1018 --- /dev/null +++ b/engine/lib/upgrades/2011052801.php @@ -0,0 +1,46 @@ +<?php +/** + * Make sure all users have the relationship member_of_site + */ +global $ENTITY_CACHE; +$db_prefix = get_config('dbprefix'); + +$limit = 100; + +$q = "SELECT e.* FROM {$db_prefix}entities e + WHERE e.type = 'user' AND e.guid NOT IN ( + SELECT guid_one FROM {$db_prefix}entity_relationships + WHERE guid_two = 1 AND relationship = 'member_of_site' + ) + LIMIT $limit"; + +$users = get_data($q); + +while ($users) { + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); + + // do manually to not trigger any events because these aren't new users. + foreach ($users as $user) { + $rel_q = "INSERT INTO {$db_prefix}entity_relationships VALUES ( + '', + '$user->guid', + 'member_of_site', + '$user->site_guid', + '$user->time_created' + )"; + + insert_data($rel_q); + } + + // every time we run this query we've just reduced the rows it returns by $limit + // so don't pass an offset. + $q = "SELECT e.* FROM {$db_prefix}entities e + WHERE e.type = 'user' AND e.guid NOT IN ( + SELECT guid_one FROM {$db_prefix}entity_relationships + WHERE guid_two = 1 AND relationship = 'member_of_site' + ) + LIMIT $limit"; + + $users = get_data($q); +}
\ No newline at end of file diff --git a/engine/lib/upgrades/2011061200-1.8b1-sites_need_a_site_guid-6d9dcbf46c0826cc.php b/engine/lib/upgrades/2011061200-1.8b1-sites_need_a_site_guid-6d9dcbf46c0826cc.php new file mode 100644 index 000000000..41ab29998 --- /dev/null +++ b/engine/lib/upgrades/2011061200-1.8b1-sites_need_a_site_guid-6d9dcbf46c0826cc.php @@ -0,0 +1,31 @@ +<?php +/** + * Elgg 1.8b1 upgrade 2011061200 + * sites_need_a_site_guid + * + * Sites did not have a site guid. This causes problems with getting + * metadata on site objects since we default to the current site. + */ + +global $CONFIG; + +$ia = elgg_set_ignore_access(true); +$access_status = access_get_show_hidden_status(); +access_show_hidden_entities(true); + +$options = array( + 'type' => 'site', + 'site_guid' => 0, + 'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { + if (!$entity->site_guid) { + update_data("UPDATE {$CONFIG->dbprefix}entities SET site_guid=$entity->guid + WHERE guid=$entity->guid"); + } +} + +access_show_hidden_entities($access_status); +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2011092500-1.8.0.1-forum_reply_river_view-5758ce8d86ac56ce.php b/engine/lib/upgrades/2011092500-1.8.0.1-forum_reply_river_view-5758ce8d86ac56ce.php new file mode 100644 index 000000000..3a9200b51 --- /dev/null +++ b/engine/lib/upgrades/2011092500-1.8.0.1-forum_reply_river_view-5758ce8d86ac56ce.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.0.1 upgrade 2011092500 + * forum_reply_river_view + * + * The forum reply river view is in a new location in Elgg 1.8 + */ + +$query = "UPDATE {$CONFIG->dbprefix}river SET view='river/annotation/group_topic_post/reply', + action_type='reply' + WHERE view='river/forum/create' AND action_type='create'"; +update_data($query); diff --git a/engine/lib/upgrades/2011123100-1.8.2-fix_friend_river-b17e7ff8345c2269.php b/engine/lib/upgrades/2011123100-1.8.2-fix_friend_river-b17e7ff8345c2269.php new file mode 100644 index 000000000..4dc43cd32 --- /dev/null +++ b/engine/lib/upgrades/2011123100-1.8.2-fix_friend_river-b17e7ff8345c2269.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.2 upgrade 2011123100 + * fix_friend_river + * + * Action type was incorrect due to previoud friends river upgrade + */ + +$query = "UPDATE {$CONFIG->dbprefix}river + SET action_type='friend' + WHERE view='river/relationship/friend/create' AND action_type='create'"; +update_data($query); diff --git a/engine/lib/upgrades/2011123101-1.8.2-fix_blog_status-b14c2a0e7b9e7d55.php b/engine/lib/upgrades/2011123101-1.8.2-fix_blog_status-b14c2a0e7b9e7d55.php new file mode 100644 index 000000000..e351c6ac9 --- /dev/null +++ b/engine/lib/upgrades/2011123101-1.8.2-fix_blog_status-b14c2a0e7b9e7d55.php @@ -0,0 +1,25 @@ +<?php +/** + * Elgg 1.8.2 upgrade 2011123101 + * fix_blog_status + * + * Most blog posts did not have their status properly set with 1.8 upgrade so we run + * the blog status upgrade again + */ + +$ia = elgg_set_ignore_access(true); +$options = array( + 'type' => 'object', + 'subtype' => 'blog', + 'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { + if (!$entity->status) { + // create metadata owned by the original owner + create_metadata($entity->getGUID(), 'status', 'published', '', $entity->owner_guid, + $entity->access_id); + } +} +elgg_set_ignore_access($ia);
\ No newline at end of file diff --git a/engine/lib/upgrades/2012012000-1.8.3-ip_in_syslog-87fe0f068cf62428.php b/engine/lib/upgrades/2012012000-1.8.3-ip_in_syslog-87fe0f068cf62428.php new file mode 100644 index 000000000..b9514e156 --- /dev/null +++ b/engine/lib/upgrades/2012012000-1.8.3-ip_in_syslog-87fe0f068cf62428.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012012000 + * ip_in_syslog + * + * Adds a field for an IP address in the system log table + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}system_log ADD ip_address VARCHAR(15) NOT NULL AFTER time_created"; + +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2012012100-1.8.3-system_cache-93100e7d55a24a11.php b/engine/lib/upgrades/2012012100-1.8.3-system_cache-93100e7d55a24a11.php new file mode 100644 index 000000000..3a9aae2a1 --- /dev/null +++ b/engine/lib/upgrades/2012012100-1.8.3-system_cache-93100e7d55a24a11.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012012100 + * system_cache + * + * Convert viewpath cache to system cache + */ + +$value = datalist_get('viewpath_cache_enabled'); +datalist_set('system_cache_enabled', $value); + +$query = "DELETE FROM {$CONFIG->dbprefix}datalists WHERE name='viewpath_cache_enabled'"; +delete_data($query); diff --git a/engine/lib/upgrades/2012041800-1.8.3-dont_filter_passwords-c0ca4a18b38ae2bc.php b/engine/lib/upgrades/2012041800-1.8.3-dont_filter_passwords-c0ca4a18b38ae2bc.php new file mode 100644 index 000000000..b82ffbebf --- /dev/null +++ b/engine/lib/upgrades/2012041800-1.8.3-dont_filter_passwords-c0ca4a18b38ae2bc.php @@ -0,0 +1,11 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012041800 + * dont_filter_passwords + * + * Add admin notice that password handling has changed and if + * users can't login to have them reset their passwords. + */ +elgg_add_admin_notice('dont_filter_passwords', 'Password handling has been updated to be more secure and flexible. ' + . 'This change may prevent a small number of users from logging in with their existing passwords. ' + . 'If a user is unable to log in, please advise him or her to reset their password, or reset it as an admin user.'); diff --git a/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php new file mode 100644 index 000000000..780038c32 --- /dev/null +++ b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012041801 + * multiple_user_tokens + * + * Fixes https://github.com/elgg/elgg/issues/4291 + * Removes the unique index on users_apisessions for user_guid and site_guid + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}users_apisessions DROP INDEX user_guid, + ADD INDEX user_guid (user_guid, site_guid)"; +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php new file mode 100644 index 000000000..8eccf05e2 --- /dev/null +++ b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php @@ -0,0 +1,24 @@ +<?php +/** + * Elgg 1.8.14 upgrade 2013030600 + * update_user_location + * + * Before Elgg 1.8, a location like "London, England" would be stored as an array. + * This script turns that back into a string. + */ + +$ia = elgg_set_ignore_access(true); +$options = array( + 'type' => 'user', + 'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { + _elgg_invalidate_query_cache(); + + if (is_array($entity->location)) { + $entity->location = implode(', ', $entity->location); + } +} +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php new file mode 100644 index 000000000..ee99bdbc8 --- /dev/null +++ b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php @@ -0,0 +1,28 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013051700 + * add_missing_group_index + * + * Some Elgg sites are missing the groups_entity full text index on name and + * description. This checks if it exists and adds it if it does not. + */ + +$db_prefix = elgg_get_config('dbprefix'); + +$full_text_index_exists = false; +$results = get_data("SHOW INDEX FROM {$db_prefix}groups_entity"); +if ($results) { + foreach ($results as $result) { + if ($result->Index_type === 'FULLTEXT') { + $full_text_index_exists = true; + } + } +} + +if ($full_text_index_exists == false) { + $query = "ALTER TABLE {$db_prefix}groups_entity + ADD FULLTEXT name_2 (name, description)"; + if (!update_data($query)) { + elgg_log("Failed to add full text index to groups_entity table", 'ERROR'); + } +} diff --git a/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php new file mode 100644 index 000000000..d333a6cd2 --- /dev/null +++ b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013052900 + * ipv6_in_syslog + * + * Upgrade the ip column in system_log to be able to store ipv6 addresses + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}system_log MODIFY COLUMN ip_address varchar(46) NOT NULL"; + +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php new file mode 100644 index 000000000..538d74dd6 --- /dev/null +++ b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php @@ -0,0 +1,16 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013060900 + * site_secret + * + * Description + */ + +$strength = _elgg_get_site_secret_strength(); + +if ($strength !== 'strong') { + // a new key is needed immediately + register_translations(elgg_get_root_path() . 'languages/'); + + elgg_add_admin_notice('weak_site_key', elgg_echo("upgrade:site_secret_warning:$strength")); +} diff --git a/engine/lib/upgrades/create_upgrade.php b/engine/lib/upgrades/create_upgrade.php new file mode 100644 index 000000000..b34f31b7e --- /dev/null +++ b/engine/lib/upgrades/create_upgrade.php @@ -0,0 +1,152 @@ +<?php +/** + * Creates an upgrade file for Elgg. + * + * Run this from the command line: + * php create_upgrade.php upgrade_name + */ + +error_reporting(E_NOTICE); + +// only allow from the command line. +if (php_sapi_name() != 'cli') { + die('Upgrades can only be created from the command line.'); +} + +if (count($argv) < 2) { + elgg_create_upgrade_show_usage('No upgrade name.'); +} + +$name = $argv[1]; + +if (strlen($name) > 24) { + elgg_create_upgrade_show_usage('Upgrade names cannot be longer than 24 characters.'); +} + +require_once '../../../version.php'; +require_once '../elgglib.php'; +$upgrade_path = dirname(__FILE__); + +$upgrade_name = strtolower($name); +$upgrade_name = str_replace(array(' ', '-'), '_', $upgrade_name); +$upgrade_release = str_replace(array(' ', '-'), '_', $release); +$time = time(); +$upgrade_rnd = substr(md5($time), 0, 16); +$upgrade_date = date('Ymd', $time); + +// determine the inc count +$upgrade_inc = 0; +$files = elgg_get_file_list($upgrade_path); +sort($files); + +foreach ($files as $filename) { + $filename = basename($filename); + $date = (int)substr($filename, 0, 8); + $inc = (int)substr($filename, 8, 2); + + if ($upgrade_date == $date) { + if ($inc >= $upgrade_inc) { + $upgrade_inc = $inc + 1; + } + } +} + +// zero-pad +// if there are more than 10 upgrades in a day, someone needs talking to. +if ($upgrade_inc < 10) { + $upgrade_inc = "0$upgrade_inc"; +} + +$upgrade_version = $upgrade_date . $upgrade_inc; + +// make filename +if (substr($release, 0, 3) == '1.7') { + // 1.7 upgrades are YYYYMMDDXX + $upgrade_name = $upgrade_version . '.php'; +} else { + // 1.8+ upgrades are YYYYMMDDXX-release-friendly_name-rnd + $upgrade_name = $upgrade_version . "-$upgrade_release-$name-$upgrade_rnd.php"; +} + +$upgrade_file = $upgrade_path . '/' . $upgrade_name; + +if (is_file($upgrade_file)) { + elgg_create_upgrade_show_usage("Upgrade file $upgrade_file already exists. This script has failed you."); +} + +$upgrade_code = <<<___UPGRADE +<?php +/** + * Elgg $release upgrade $upgrade_version + * $name + * + * Description + */ + +// upgrade code here. + +___UPGRADE; + +$h = fopen($upgrade_file, 'wb'); + +if (!$h) { + die("Could not open file $upgrade_file"); +} + +if (!fwrite($h, $upgrade_code)) { + die("Could not write to $upgrade_file"); +} else { + elgg_set_version_dot_php_version($upgrade_version); + echo <<<___MSG + +Created upgrade file and updated version.php. + +Upgrade file: $upgrade_name +Version: $upgrade_version + +___MSG; +} + +fclose($h); + + +function elgg_set_version_dot_php_version($version) { + $file = '../../../version.php'; + $h = fopen($file, 'r+b'); + + if (!$h) { + return false; + } + + $out = ''; + + while (($line = fgets($h)) !== false) { + $find = "/\\\$version[ ]?=[ ]?[0-9]{10};/"; + $replace = "\$version = $version;"; + $out .= preg_replace($find, $replace, $line); + } + + rewind($h); + + fwrite($h, $out); + fclose($h); + return true; +} + +/** + * Shows the usage for the create_upgrade script and dies(). + * + * @param string $msg Optional message to display + * @return void + */ +function elgg_create_upgrade_show_usage($msg = '') { + $text = <<<___MSG +$msg + +Example: + php create_upgrade.php my_upgrade + +___MSG; + + die($text); +} diff --git a/engine/lib/user_settings.php b/engine/lib/user_settings.php new file mode 100644 index 000000000..0e36dc46d --- /dev/null +++ b/engine/lib/user_settings.php @@ -0,0 +1,360 @@ +<?php +/** + * Elgg user settings functions. + * Functions for adding and manipulating options on the user settings panel. + * + * @package Elgg.Core + * @subpackage Settings.User + */ + +/** + * Saves user settings. + * + * @todo this assumes settings are coming in on a GET/POST request + * + * @note This is a handler for the 'usersettings:save', 'user' plugin hook + * + * @return void + * @access private + */ +function users_settings_save() { + elgg_set_user_language(); + elgg_set_user_password(); + elgg_set_user_default_access(); + elgg_set_user_name(); + elgg_set_user_email(); +} + +/** + * Set a user's password + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_password() { + $current_password = get_input('current_password', null, false); + $password = get_input('password', null, false); + $password2 = get_input('password2', null, false); + $user_guid = get_input('guid'); + + if (!$user_guid) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_guid); + } + + if ($user && $password) { + // let admin user change anyone's password without knowing it except his own. + if (!elgg_is_admin_logged_in() || elgg_is_admin_logged_in() && $user->guid == elgg_get_logged_in_user_guid()) { + $credentials = array( + 'username' => $user->username, + 'password' => $current_password + ); + + try { + pam_auth_userpass($credentials); + } catch (LoginException $e) { + register_error(elgg_echo('LoginException:ChangePasswordFailure')); + return false; + } + } + + try { + $result = validate_password($password); + } catch (RegistrationException $e) { + register_error($e->getMessage()); + return false; + } + + if ($result) { + if ($password == $password2) { + $user->salt = generate_random_cleartext_password(); // Reset the salt + $user->password = generate_user_password($user, $password); + if ($user->save()) { + system_message(elgg_echo('user:password:success')); + return true; + } else { + register_error(elgg_echo('user:password:fail')); + } + } else { + register_error(elgg_echo('user:password:fail:notsame')); + } + } else { + register_error(elgg_echo('user:password:fail:tooshort')); + } + } else { + // no change + return null; + } + + return false; +} + +/** + * Set a user's display name + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_name() { + $name = strip_tags(get_input('name')); + $user_id = get_input('guid'); + + if (!$user_id) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_id); + } + + if (elgg_strlen($name) > 50) { + register_error(elgg_echo('user:name:fail')); + return false; + } + + if (($user) && ($user->canEdit()) && ($name)) { + if ($name != $user->name) { + $user->name = $name; + if ($user->save()) { + system_message(elgg_echo('user:name:success')); + return true; + } else { + register_error(elgg_echo('user:name:fail')); + } + } else { + // no change + return null; + } + } else { + register_error(elgg_echo('user:name:fail')); + } + return false; +} + +/** + * Set a user's language + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_language() { + $language = get_input('language'); + $user_id = get_input('guid'); + + if (!$user_id) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_id); + } + + if (($user) && ($language)) { + if (strcmp($language, $user->language) != 0) { + $user->language = $language; + if ($user->save()) { + system_message(elgg_echo('user:language:success')); + return true; + } else { + register_error(elgg_echo('user:language:fail')); + } + } else { + // no change + return null; + } + } else { + register_error(elgg_echo('user:language:fail')); + } + return false; +} + +/** + * Set a user's email address + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_email() { + $email = get_input('email'); + $user_id = get_input('guid'); + + if (!$user_id) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_id); + } + + if (!is_email_address($email)) { + register_error(elgg_echo('email:save:fail')); + return false; + } + + if ($user) { + if (strcmp($email, $user->email) != 0) { + if (!get_user_by_email($email)) { + if ($user->email != $email) { + + $user->email = $email; + if ($user->save()) { + system_message(elgg_echo('email:save:success')); + return true; + } else { + register_error(elgg_echo('email:save:fail')); + } + } + } else { + register_error(elgg_echo('registration:dupeemail')); + } + } else { + // no change + return null; + } + } else { + register_error(elgg_echo('email:save:fail')); + } + return false; +} + +/** + * Set a user's default access level + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_default_access() { + + if (!elgg_get_config('allow_user_default_access')) { + return false; + } + + $default_access = get_input('default_access'); + $user_id = get_input('guid'); + + if (!$user_id) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_id); + } + + if ($user) { + $current_default_access = $user->getPrivateSetting('elgg_default_access'); + if ($default_access !== $current_default_access) { + if ($user->setPrivateSetting('elgg_default_access', $default_access)) { + system_message(elgg_echo('user:default_access:success')); + return true; + } else { + register_error(elgg_echo('user:default_access:fail')); + } + } else { + // no change + return null; + } + } else { + register_error(elgg_echo('user:default_access:fail')); + } + + return false; +} + +/** + * Set up the menu for user settings + * + * @return void + * @access private + */ +function usersettings_pagesetup() { + $user = elgg_get_page_owner_entity(); + + if ($user && elgg_get_context() == "settings") { + $params = array( + 'name' => '1_account', + 'text' => elgg_echo('usersettings:user:opt:linktext'), + 'href' => "settings/user/{$user->username}", + ); + elgg_register_menu_item('page', $params); + $params = array( + 'name' => '1_plugins', + 'text' => elgg_echo('usersettings:plugins:opt:linktext'), + 'href' => "settings/plugins/{$user->username}", + ); + elgg_register_menu_item('page', $params); + $params = array( + 'name' => '1_statistics', + 'text' => elgg_echo('usersettings:statistics:opt:linktext'), + 'href' => "settings/statistics/{$user->username}", + ); + elgg_register_menu_item('page', $params); + } +} + +/** + * Page handler for user settings + * + * @param array $page Pages array + * + * @return bool + * @access private + */ +function usersettings_page_handler($page) { + global $CONFIG; + + if (!isset($page[0])) { + $page[0] = 'user'; + } + + if (isset($page[1])) { + $user = get_user_by_username($page[1]); + elgg_set_page_owner_guid($user->guid); + } else { + $user = elgg_get_logged_in_user_entity(); + elgg_set_page_owner_guid($user->guid); + } + + elgg_push_breadcrumb(elgg_echo('settings'), "settings/user/$user->username"); + + switch ($page[0]) { + case 'statistics': + elgg_push_breadcrumb(elgg_echo('usersettings:statistics:opt:linktext')); + $path = $CONFIG->path . "pages/settings/statistics.php"; + break; + case 'plugins': + elgg_push_breadcrumb(elgg_echo('usersettings:plugins:opt:linktext')); + $path = $CONFIG->path . "pages/settings/tools.php"; + break; + case 'user': + $path = $CONFIG->path . "pages/settings/account.php"; + break; + } + + if (isset($path)) { + require $path; + return true; + } + return false; +} + +/** + * Initialize the user settings library + * + * @return void + * @access private + */ +function usersettings_init() { + elgg_register_page_handler('settings', 'usersettings_page_handler'); + + elgg_register_plugin_hook_handler('usersettings:save', 'user', 'users_settings_save'); + + elgg_register_action("usersettings/save"); + + // extend the account settings form + elgg_extend_view('forms/account/settings', 'core/settings/account/name', 100); + elgg_extend_view('forms/account/settings', 'core/settings/account/password', 100); + elgg_extend_view('forms/account/settings', 'core/settings/account/email', 100); + elgg_extend_view('forms/account/settings', 'core/settings/account/language', 100); + elgg_extend_view('forms/account/settings', 'core/settings/account/default_access', 100); +} + +elgg_register_event_handler('init', 'system', 'usersettings_init'); +elgg_register_event_handler('pagesetup', 'system', 'usersettings_pagesetup'); diff --git a/engine/lib/users.php b/engine/lib/users.php index 1d08bd133..a8fb9121c 100644 --- a/engine/lib/users.php +++ b/engine/lib/users.php @@ -3,384 +3,25 @@ * Elgg users * Functions to manage multiple or single users in an Elgg install * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage DataModel.User */ /// Map a username to a cached GUID +global $USERNAME_TO_GUID_MAP_CACHE; $USERNAME_TO_GUID_MAP_CACHE = array(); /// Map a user code to a cached GUID +global $CODE_TO_GUID_MAP_CACHE; $CODE_TO_GUID_MAP_CACHE = array(); /** - * ElggUser - * - * Representation of a "user" in the system. - * - * @package Elgg - * @subpackage Core - */ -class ElggUser extends ElggEntity - implements Friendable { - /** - * Initialise the attributes array. - * This is vital to distinguish between metadata and base parameters. - * - * Place your base parameters here. - */ - protected function initialise_attributes() { - parent::initialise_attributes(); - - $this->attributes['type'] = "user"; - $this->attributes['name'] = ""; - $this->attributes['username'] = ""; - $this->attributes['password'] = ""; - $this->attributes['salt'] = ""; - $this->attributes['email'] = ""; - $this->attributes['language'] = ""; - $this->attributes['code'] = ""; - $this->attributes['banned'] = "no"; - $this->attributes['tables_split'] = 2; - } - - /** - * Construct a new user entity, optionally from a given id value. - * - * @param mixed $guid If an int, load that GUID. - * If a db row then will attempt to load the rest of the data. - * @throws Exception if there was a problem creating the user. - */ - function __construct($guid = null) { - $this->initialise_attributes(); - - if (!empty($guid)) { - // Is $guid is a DB row - either a entity row, or a user table row. - if ($guid instanceof stdClass) { - // Load the rest - if (!$this->load($guid->guid)) { - throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid)); - } - } - - // See if this is a username - else if (is_string($guid)) { - $guid = get_user_by_username($guid); - foreach ($guid->attributes as $key => $value) { - $this->attributes[$key] = $value; - } - } - - // Is $guid is an ElggUser? Use a copy constructor - else if ($guid instanceof ElggUser) { - elgg_deprecated_notice('This type of usage of the ElggUser constructor was deprecated. Please use the clone method.', 1.7); - - foreach ($guid->attributes as $key => $value) { - $this->attributes[$key] = $value; - } - } - - // Is this is an ElggEntity but not an ElggUser = ERROR! - else if ($guid instanceof ElggEntity) { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggUser')); - } - - // We assume if we have got this far, $guid is an int - else if (is_numeric($guid)) { - if (!$this->load($guid)) { - IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid)); - } - } - - else { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); - } - } - } - - /** - * Override the load function. - * This function will ensure that all data is loaded (were possible), so - * if only part of the ElggUser is loaded, it'll load the rest. - * - * @param int $guid - * @return true|false - */ - protected function load($guid) { - // Test to see if we have the generic stuff - if (!parent::load($guid)) { - return false; - } - - // Check the type - if ($this->attributes['type']!='user') { - throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class())); - } - - // Load missing data - $row = get_user_entity_as_row($guid); - if (($row) && (!$this->isFullyLoaded())) { - // If $row isn't a cached copy then increment the counter - $this->attributes['tables_loaded'] ++; - } - - // Now put these into the attributes array as core values - $objarray = (array) $row; - foreach($objarray as $key => $value) { - $this->attributes[$key] = $value; - } - - return true; - } - - /** - * Saves this user to the database. - * @return true|false - */ - public function save() { - // Save generic stuff - if (!parent::save()) { - return false; - } - - // Now save specific stuff - return create_user_entity($this->get('guid'), $this->get('name'), $this->get('username'), $this->get('password'), $this->get('salt'), $this->get('email'), $this->get('language'), $this->get('code')); - } - - /** - * User specific override of the entity delete method. - * - * @return bool - */ - public function delete() { - global $USERNAME_TO_GUID_MAP_CACHE, $CODE_TO_GUID_MAP_CACHE; - - // clear cache - if (isset($USERNAME_TO_GUID_MAP_CACHE[$this->username])) { - unset($USERNAME_TO_GUID_MAP_CACHE[$this->username]); - } - if (isset($CODE_TO_GUID_MAP_CACHE[$this->code])) { - unset($CODE_TO_GUID_MAP_CACHE[$this->code]); - } - - // Delete owned data - clear_annotations_by_owner($this->guid); - clear_metadata_by_owner($this->guid); - clear_user_files($this); - - // Delete entity - return parent::delete(); - } - - /** - * Ban this user. - * - * @param string $reason Optional reason - */ - public function ban($reason = "") { - return ban_user($this->guid, $reason); - } - - /** - * Unban this user. - */ - public function unban() { - return unban_user($this->guid); - } - - /** - * Is this user banned or not? - * - * @return bool - */ - public function isBanned() { - return $this->banned == 'yes'; - } - - /** - * Get sites that this user is a member of - * - * @param string $subtype Optionally, the subtype of result we want to limit to - * @param int $limit The number of results to return - * @param int $offset Any indexing offset - */ - function getSites($subtype="", $limit = 10, $offset = 0) { - // return get_site_users($this->getGUID(), $subtype, $limit, $offset); - return get_user_sites($this->getGUID(), $subtype, $limit, $offset); - } - - /** - * Add this user to a particular site - * - * @param int $site_guid The guid of the site to add it to - * @return true|false - */ - function addToSite($site_guid) { - // return add_site_user($this->getGUID(), $site_guid); - return add_site_user($site_guid, $this->getGUID()); - } - - /** - * Remove this user from a particular site - * - * @param int $site_guid The guid of the site to remove it from - * @return true|false - */ - function removeFromSite($site_guid) { - //return remove_site_user($this->getGUID(), $site_guid); - return remove_site_user($site_guid, $this->getGUID()); - } - - /** - * Adds a user to this user's friends list - * - * @param int $friend_guid The GUID of the user to add - * @return true|false Depending on success - */ - function addFriend($friend_guid) { - return user_add_friend($this->getGUID(), $friend_guid); - } - - /** - * Removes a user from this user's friends list - * - * @param int $friend_guid The GUID of the user to remove - * @return true|false Depending on success - */ - function removeFriend($friend_guid) { - return user_remove_friend($this->getGUID(), $friend_guid); - } - - /** - * Determines whether or not this user is a friend of the currently logged in user - * - * @return true|false - */ - function isFriend() { - return user_is_friend(get_loggedin_userid(), $this->getGUID()); - } - - /** - * Determines whether this user is friends with another user - * - * @param int $user_guid The GUID of the user to check is on this user's friends list - * @return true|false - */ - function isFriendsWith($user_guid) { - return user_is_friend($this->getGUID(), $user_guid); - } - - /** - * Determines whether or not this user is on another user's friends list - * - * @param int $user_guid The GUID of the user to check against - * @return true|false - */ - function isFriendOf($user_guid) { - return user_is_friend($user_guid, $this->getGUID()); - } - - /** - * Retrieves a list of this user's friends - * - * @param string $subtype Optionally, the subtype of user to filter to (leave blank for all) - * @param int $limit The number of users to retrieve - * @param int $offset Indexing offset, if any - * @return array|false Array of ElggUsers, or false, depending on success - */ - function getFriends($subtype = "", $limit = 10, $offset = 0) { - return get_user_friends($this->getGUID(), $subtype, $limit, $offset); - } - - /** - * Retrieves a list of people who have made this user a friend - * - * @param string $subtype Optionally, the subtype of user to filter to (leave blank for all) - * @param int $limit The number of users to retrieve - * @param int $offset Indexing offset, if any - * @return array|false Array of ElggUsers, or false, depending on success - */ - function getFriendsOf($subtype = "", $limit = 10, $offset = 0) { - return get_user_friends_of($this->getGUID(), $subtype, $limit, $offset); - } - - /** - * Get an array of ElggObjects owned by this user. - * - * @param string $subtype The subtype of the objects, if any - * @param int $limit Number of results to return - * @param int $offset Any indexing offset - */ - public function getObjects($subtype="", $limit = 10, $offset = 0) { - return get_user_objects($this->getGUID(), $subtype, $limit, $offset); - } - - /** - * Get an array of ElggObjects owned by this user's friends. - * - * @param string $subtype The subtype of the objects, if any - * @param int $limit Number of results to return - * @param int $offset Any indexing offset - */ - public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0) { - return get_user_friends_objects($this->getGUID(), $subtype, $limit, $offset); - } - - /** - * Counts the number of ElggObjects owned by this user - * - * @param string $subtype The subtypes of the objects, if any - * @return int The number of ElggObjects - */ - public function countObjects($subtype = "") { - return count_user_objects($this->getGUID(), $subtype); - } - - /** - * Get the collections associated with a user. - * - * @param string $subtype Optionally, the subtype of result we want to limit to - * @param int $limit The number of results to return - * @param int $offset Any indexing offset - * @return unknown - */ - public function getCollections($subtype="", $limit = 10, $offset = 0) { - return get_user_collections($this->getGUID(), $subtype, $limit, $offset); - } - - /** - * If a user's owner is blank, return its own GUID as the owner - * - * @return int User GUID - */ - function getOwner() { - if ($this->owner_guid == 0) { - return $this->getGUID(); - } - - return $this->owner_guid; - } - - // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// - - /** - * Return an array of fields which can be exported. - */ - public function getExportableValues() { - return array_merge(parent::getExportableValues(), array( - 'name', - 'username', - 'language', - )); - } -} - -/** * Return the user specific details of a user by a row. * - * @param int $guid + * @param int $guid The ElggUser guid + * + * @return mixed + * @access private */ function get_user_entity_as_row($guid) { global $CONFIG; @@ -390,13 +31,20 @@ function get_user_entity_as_row($guid) { } /** - * Create or update the extras table for a given user. + * Create or update the entities table for a given user. * Call create_entity first. * - * @param int $guid - * @param string $name - * @param string $description - * @param string $url + * @param int $guid The user's GUID + * @param string $name The user's display name + * @param string $username The username + * @param string $password The password + * @param string $salt A salt for the password + * @param string $email The user's email address + * @param string $language The user's default language + * @param string $code A code + * + * @return bool + * @access private */ function create_user_entity($guid, $name, $username, $password, $salt, $email, $language, $code) { global $CONFIG; @@ -413,27 +61,36 @@ function create_user_entity($guid, $name, $username, $password, $salt, $email, $ $row = get_entity_as_row($guid); if ($row) { // Exists and you have access to it - - if ($exists = get_data_row("SELECT guid from {$CONFIG->dbprefix}users_entity where guid = {$guid}")) { - $result = update_data("UPDATE {$CONFIG->dbprefix}users_entity set name='$name', username='$username', password='$password', salt='$salt', email='$email', language='$language', code='$code', last_action = ". time() ." where guid = {$guid}"); + $query = "SELECT guid from {$CONFIG->dbprefix}users_entity where guid = {$guid}"; + if ($exists = get_data_row($query)) { + $query = "UPDATE {$CONFIG->dbprefix}users_entity + SET name='$name', username='$username', password='$password', salt='$salt', + email='$email', language='$language', code='$code' + WHERE guid = $guid"; + + $result = update_data($query); if ($result != false) { // Update succeeded, continue $entity = get_entity($guid); - if (trigger_elgg_event('update',$entity->type,$entity)) { + if (elgg_trigger_event('update', $entity->type, $entity)) { return $guid; } else { $entity->delete(); } } } else { - // Update failed, attempt an insert. - $result = insert_data("INSERT into {$CONFIG->dbprefix}users_entity (guid, name, username, password, salt, email, language, code) values ($guid, '$name', '$username', '$password', '$salt', '$email', '$language', '$code')"); - if ($result!==false) { + // Exists query failed, attempt an insert. + $query = "INSERT into {$CONFIG->dbprefix}users_entity + (guid, name, username, password, salt, email, language, code) + values ($guid, '$name', '$username', '$password', '$salt', '$email', '$language', '$code')"; + + $result = insert_data($query); + if ($result !== false) { $entity = get_entity($guid); - if (trigger_elgg_event('create',$entity->type,$entity)) { + if (elgg_trigger_event('create', $entity->type, $entity)) { return $guid; } else { - $entity->delete(); //delete_entity($guid); + $entity->delete(); } } } @@ -446,15 +103,20 @@ function create_user_entity($guid, $name, $username, $password, $salt, $email, $ * Disables all of a user's entities * * @param int $owner_guid The owner GUID - * @return true|false Depending on success + * + * @return bool Depending on success */ function disable_user_entities($owner_guid) { global $CONFIG; $owner_guid = (int) $owner_guid; if ($entity = get_entity($owner_guid)) { - if (trigger_elgg_event('disable',$entity->type,$entity)) { + if (elgg_trigger_event('disable', $entity->type, $entity)) { if ($entity->canEdit()) { - $res = update_data("UPDATE {$CONFIG->dbprefix}entities set enabled='no' where owner_guid={$owner_guid} or container_guid = {$owner_guid}"); + $query = "UPDATE {$CONFIG->dbprefix}entities + set enabled='no' where owner_guid={$owner_guid} + or container_guid = {$owner_guid}"; + + $res = update_data($query); return $res; } } @@ -466,22 +128,23 @@ function disable_user_entities($owner_guid) { /** * Ban a user * - * @param int $user_guid The user guid - * @param string $reason A reason + * @param int $user_guid The user guid + * @param string $reason A reason + * + * @return bool */ function ban_user($user_guid, $reason = "") { global $CONFIG; $user_guid = (int)$user_guid; - $reason = sanitise_string($reason); $user = get_entity($user_guid); if (($user) && ($user->canEdit()) && ($user instanceof ElggUser)) { - if (trigger_elgg_event('ban', 'user', $user)) { + if (elgg_trigger_event('ban', 'user', $user)) { // Add reason if ($reason) { - create_metadata($user_guid, 'ban_reason', $reason,'', 0, ACCESS_PUBLIC); + create_metadata($user_guid, 'ban_reason', $reason, '', 0, ACCESS_PUBLIC); } // clear "remember me" cookie code so user cannot login in using it @@ -499,17 +162,22 @@ function ban_user($user_guid, $reason = "") { } // Set ban flag - return update_data("UPDATE {$CONFIG->dbprefix}users_entity set banned='yes' where guid=$user_guid"); + $query = "UPDATE {$CONFIG->dbprefix}users_entity set banned='yes' where guid=$user_guid"; + return update_data($query); } + + return FALSE; } - return false; + return FALSE; } /** * Unban a user. * * @param int $user_guid Unban a user. + * + * @return bool */ function unban_user($user_guid) { global $CONFIG; @@ -519,8 +187,8 @@ function unban_user($user_guid) { $user = get_entity($user_guid); if (($user) && ($user->canEdit()) && ($user instanceof ElggUser)) { - if (trigger_elgg_event('unban', 'user', $user)) { - create_metadata($user_guid, 'ban_reason', '','', 0, ACCESS_PUBLIC); + if (elgg_trigger_event('unban', 'user', $user)) { + create_metadata($user_guid, 'ban_reason', '', '', 0, ACCESS_PUBLIC); // invalidate memcache for this user static $newentity_cache; @@ -532,33 +200,97 @@ function unban_user($user_guid) { $newentity_cache->delete($user_guid); } - return update_data("UPDATE {$CONFIG->dbprefix}users_entity set banned='no' where guid=$user_guid"); + + $query = "UPDATE {$CONFIG->dbprefix}users_entity set banned='no' where guid=$user_guid"; + return update_data($query); } + + return FALSE; } - return false; + return FALSE; +} + +/** + * Makes user $guid an admin. + * + * @param int $user_guid User guid + * + * @return bool + */ +function make_user_admin($user_guid) { + global $CONFIG; + + $user = get_entity((int)$user_guid); + + if (($user) && ($user instanceof ElggUser) && ($user->canEdit())) { + if (elgg_trigger_event('make_admin', 'user', $user)) { + + // invalidate memcache for this user + static $newentity_cache; + if ((!$newentity_cache) && (is_memcache_available())) { + $newentity_cache = new ElggMemcache('new_entity_cache'); + } + + if ($newentity_cache) { + $newentity_cache->delete($user_guid); + } + + $r = update_data("UPDATE {$CONFIG->dbprefix}users_entity set admin='yes' where guid=$user_guid"); + _elgg_invalidate_cache_for_entity($user_guid); + return $r; + } + + return FALSE; + } + + return FALSE; } /** - * THIS FUNCTION IS DEPRECATED. + * Removes user $guid's admin flag. * - * Delete a user's extra data. + * @param int $user_guid User GUID * - * @param int $guid + * @return bool */ -function delete_user_entity($guid) { - system_message(sprintf(elgg_echo('deprecatedfunction'), 'delete_user_entity')); +function remove_user_admin($user_guid) { + global $CONFIG; + + $user = get_entity((int)$user_guid); + + if (($user) && ($user instanceof ElggUser) && ($user->canEdit())) { + if (elgg_trigger_event('remove_admin', 'user', $user)) { + + // invalidate memcache for this user + static $newentity_cache; + if ((!$newentity_cache) && (is_memcache_available())) { + $newentity_cache = new ElggMemcache('new_entity_cache'); + } - return 1; // Always return that we have deleted one row in order to not break existing code. + if ($newentity_cache) { + $newentity_cache->delete($user_guid); + } + + $r = update_data("UPDATE {$CONFIG->dbprefix}users_entity set admin='no' where guid=$user_guid"); + _elgg_invalidate_cache_for_entity($user_guid); + return $r; + } + + return FALSE; + } + + return FALSE; } /** * Get the sites this user is part of * * @param int $user_guid The user's GUID - * @param int $limit Number of results to return - * @param int $offset Any indexing offset - * @return false|array On success, an array of ElggSites + * @param int $limit Number of results to return + * @param int $offset Any indexing offset + * + * @return ElggSite[]|false On success, an array of ElggSites */ function get_user_sites($user_guid, $limit = 10, $offset = 0) { $user_guid = (int)$user_guid; @@ -566,21 +298,23 @@ function get_user_sites($user_guid, $limit = 10, $offset = 0) { $offset = (int)$offset; return elgg_get_entities_from_relationship(array( + 'site_guids' => ELGG_ENTITIES_ANY_VALUE, 'relationship' => 'member_of_site', 'relationship_guid' => $user_guid, 'inverse_relationship' => FALSE, - 'types' => 'site', + 'type' => 'site', 'limit' => $limit, - 'offset' => $offset) - ); + 'offset' => $offset, + )); } /** * Adds a user to another user's friends list. * - * @param int $user_guid The GUID of the friending user + * @param int $user_guid The GUID of the friending user * @param int $friend_guid The GUID of the user to friend - * @return true|false Depending on success + * + * @return bool Depending on success */ function user_add_friend($user_guid, $friend_guid) { $user_guid = (int) $user_guid; @@ -603,20 +337,21 @@ function user_add_friend($user_guid, $friend_guid) { /** * Removes a user from another user's friends list. * - * @param int $user_guid The GUID of the friending user + * @param int $user_guid The GUID of the friending user * @param int $friend_guid The GUID of the user on the friends list - * @return true|false Depending on success + * + * @return bool Depending on success */ function user_remove_friend($user_guid, $friend_guid) { - global $CONFIG; - $user_guid = (int) $user_guid; $friend_guid = (int) $friend_guid; // perform cleanup for access lists. $collections = get_user_access_collections($user_guid); - foreach ($collections as $collection) { - remove_user_from_access_collection($friend_guid, $collection->id); + if ($collections) { + foreach ($collections as $collection) { + remove_user_from_access_collection($friend_guid, $collection->id); + } } return remove_entity_relationship($user_guid, "friend", $friend_guid); @@ -625,29 +360,33 @@ function user_remove_friend($user_guid, $friend_guid) { /** * Determines whether or not a user is another user's friend. * - * @param int $user_guid The GUID of the user + * @param int $user_guid The GUID of the user * @param int $friend_guid The GUID of the friend - * @return true|false + * + * @return bool */ function user_is_friend($user_guid, $friend_guid) { - return check_entity_relationship($user_guid, "friend", $friend_guid); + return check_entity_relationship($user_guid, "friend", $friend_guid) !== false; } /** * Obtains a given user's friends * - * @param int $user_guid The user's GUID - * @param string $subtype The subtype of users, if any - * @param int $limit Number of results to return (default 10) - * @param int $offset Indexing offset, if any - * @return false|array Either an array of ElggUsers or false, depending on success + * @param int $user_guid The user's GUID + * @param string $subtype The subtype of users, if any + * @param int $limit Number of results to return (default 10) + * @param int $offset Indexing offset, if any + * + * @return ElggUser[]|false Either an array of ElggUsers or false, depending on success */ -function get_user_friends($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0) { +function get_user_friends($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$offset = 0) { + return elgg_get_entities_from_relationship(array( 'relationship' => 'friend', 'relationship_guid' => $user_guid, - 'types' => 'user', - 'subtypes' => $subtype, + 'type' => 'user', + 'subtype' => $subtype, 'limit' => $limit, 'offset' => $offset )); @@ -656,110 +395,45 @@ function get_user_friends($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit /** * Obtains the people who have made a given user a friend * - * @param int $user_guid The user's GUID - * @param string $subtype The subtype of users, if any - * @param int $limit Number of results to return (default 10) - * @param int $offset Indexing offset, if any - * @return false|array Either an array of ElggUsers or false, depending on success + * @param int $user_guid The user's GUID + * @param string $subtype The subtype of users, if any + * @param int $limit Number of results to return (default 10) + * @param int $offset Indexing offset, if any + * + * @return ElggUser[]|false Either an array of ElggUsers or false, depending on success */ -function get_user_friends_of($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0) { +function get_user_friends_of($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$offset = 0) { + return elgg_get_entities_from_relationship(array( 'relationship' => 'friend', 'relationship_guid' => $user_guid, 'inverse_relationship' => TRUE, - 'types' => 'user', - 'subtypes' => $subtype, - 'limit' => $limit, - 'offset' => $offset - )); -} - -/** - * Obtains a list of objects owned by a user - * - * @param int $user_guid The GUID of the owning user - * @param string $subtype Optionally, the subtype of objects - * @param int $limit The number of results to return (default 10) - * @param int $offset Indexing offset, if any - * @param int $timelower The earliest time the entity can have been created. Default: all - * @param int $timeupper The latest time the entity can have been created. Default: all - * @return false|array An array of ElggObjects or false, depending on success - */ -function get_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0, $timelower = 0, $timeupper = 0) { - $ntt = elgg_get_entities(array( - 'type' => 'object', + 'type' => 'user', 'subtype' => $subtype, - 'owner_guid' => $user_guid, 'limit' => $limit, - 'offset' => $offset, - 'container_guid' => $user_guid, - 'created_time_lower' => $timelower, - 'created_time_upper' => $timeupper - )); - return $ntt; -} - -/** - * Counts the objects (optionally of a particular subtype) owned by a user - * - * @param int $user_guid The GUID of the owning user - * @param string $subtype Optionally, the subtype of objects - * @param int $timelower The earliest time the entity can have been created. Default: all - * @param int $timeupper The latest time the entity can have been created. Default: all - * @return int The number of objects the user owns (of this subtype) - */ -function count_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $timelower = 0, $timeupper = 0) { - $total = elgg_get_entities(array( - 'type' => 'object', - 'subtype' => $subtype, - 'owner_guid' => $user_guid, - 'count' => TRUE, - 'container_guid' => $user_guid, - 'created_time_lower' => $timelower, - 'created_time_upper' => $timeupper + 'offset' => $offset )); - return $total; } /** - * Displays a list of user objects of a particular subtype, with navigation. + * Obtains a list of objects owned by a user's friends * - * @see elgg_view_entity_list + * @param int $user_guid The GUID of the user to get the friends of + * @param string $subtype Optionally, the subtype of objects + * @param int $limit The number of results to return (default 10) + * @param int $offset Indexing offset, if any + * @param int $timelower The earliest time the entity can have been created. Default: all + * @param int $timeupper The latest time the entity can have been created. Default: all * - * @param int $user_guid The GUID of the user - * @param string $subtype The object subtype - * @param int $limit The number of entities to display on a page - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow gallery view (default: true) - * @param true|false $pagination Whether to display pagination (default: true) - * @param int $timelower The earliest time the entity can have been created. Default: all - * @param int $timeupper The latest time the entity can have been created. Default: all - * @return string The list in a form suitable to display + * @return ElggObject[]|false An array of ElggObjects or false, depending on success */ -function list_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = true, $timelower = 0, $timeupper = 0) { - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = (int) count_user_objects($user_guid, $subtype,$timelower,$timeupper); - $entities = get_user_objects($user_guid, $subtype, $limit, $offset, $timelower, $timeupper); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); -} +function get_user_friends_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$offset = 0, $timelower = 0, $timeupper = 0) { -/** - * Obtains a list of objects owned by a user's friends - * - * @param int $user_guid The GUID of the user to get the friends of - * @param string $subtype Optionally, the subtype of objects - * @param int $limit The number of results to return (default 10) - * @param int $offset Indexing offset, if any - * @param int $timelower The earliest time the entity can have been created. Default: all - * @param int $timeupper The latest time the entity can have been created. Default: all - * @return false|array An array of ElggObjects or false, depending on success - */ -function get_user_friends_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0, $timelower = 0, $timeupper = 0) { if ($friends = get_user_friends($user_guid, "", 999999, 0)) { $friendguids = array(); - foreach($friends as $friend) { + foreach ($friends as $friend) { $friendguids[] = $friend->getGUID(); } return elgg_get_entities(array( @@ -779,16 +453,19 @@ function get_user_friends_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE /** * Counts the number of objects owned by a user's friends * - * @param int $user_guid The GUID of the user to get the friends of - * @param string $subtype Optionally, the subtype of objects - * @param int $timelower The earliest time the entity can have been created. Default: all - * @param int $timeupper The latest time the entity can have been created. Default: all + * @param int $user_guid The GUID of the user to get the friends of + * @param string $subtype Optionally, the subtype of objects + * @param int $timelower The earliest time the entity can have been created. Default: all + * @param int $timeupper The latest time the entity can have been created. Default: all + * * @return int The number of objects */ -function count_user_friends_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $timelower = 0, $timeupper = 0) { +function count_user_friends_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, +$timelower = 0, $timeupper = 0) { + if ($friends = get_user_friends($user_guid, "", 999999, 0)) { $friendguids = array(); - foreach($friends as $friend) { + foreach ($friends as $friend) { $friendguids[] = $friend->getGUID(); } return elgg_get_entities(array( @@ -809,44 +486,44 @@ function count_user_friends_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VAL * * @see elgg_view_entity_list * - * @param int $user_guid The GUID of the user - * @param string $subtype The object subtype - * @param int $limit The number of entities to display on a page - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow you to flip to gallery mode (default: true) - * @param true|false $pagination Whether to display pagination (default: true) - * @param int $timelower The earliest time the entity can have been created. Default: all - * @param int $timeupper The latest time the entity can have been created. Default: all - * @return string The list in a form suitable to display + * @param int $user_guid The GUID of the user + * @param string $subtype The object subtype + * @param int $limit The number of entities to display on a page + * @param bool $full_view Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow you to flip to gallery mode (default: true) + * @param bool $pagination Whether to display pagination (default: true) + * @param int $timelower The earliest time the entity can have been created. Default: all + * @param int $timeupper The latest time the entity can have been created. Default: all + * + * @return string */ -function list_user_friends_objects($user_guid, $subtype = "", $limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = true, $timelower = 0, $timeupper = 0) { - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = (int) count_user_friends_objects($user_guid, $subtype, $timelower, $timeupper); - $entities = get_user_friends_objects($user_guid, $subtype, $limit, $offset, $timelower, $timeupper); +function list_user_friends_objects($user_guid, $subtype = "", $limit = 10, $full_view = true, +$listtypetoggle = true, $pagination = true, $timelower = 0, $timeupper = 0) { - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); -} + $offset = (int)get_input('offset'); + $limit = (int)$limit; + $count = (int)count_user_friends_objects($user_guid, $subtype, $timelower, $timeupper); -/** - * Get user objects by an array of metadata - * - * @param int $user_guid The GUID of the owning user - * @param string $subtype Optionally, the subtype of objects - * @paran array $metadata An array of metadata - * @param int $limit The number of results to return (default 10) - * @param int $offset Indexing offset, if any - * @return false|array An array of ElggObjects or false, depending on success - */ -function get_user_objects_by_metadata($user_guid, $subtype = "", $metadata = array(), $limit = 0, $offset = 0) { - return get_entities_from_metadata_multi($metadata,"object",$subtype,$user_guid,$limit,$offset); + $entities = get_user_friends_objects($user_guid, $subtype, $limit, $offset, + $timelower, $timeupper); + + return elgg_view_entity_list($entities, array( + 'count' => $count, + 'offset' => $offset, + 'limit' => $limit, + 'full_view' => $full_view, + 'list_type_toggle' => $listtypetoggle, + 'pagination' => $pagination, + )); } /** * Get a user object from a GUID. * * This function returns an ElggUser from a given GUID. + * * @param int $guid The GUID + * * @return ElggUser|false */ function get_user($guid) { @@ -856,7 +533,6 @@ function get_user($guid) { } if ((!empty($result)) && (!($result instanceof ElggUser))) { - //throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, 'ElggUser')); return false; } @@ -871,32 +547,45 @@ function get_user($guid) { * Get user by username * * @param string $username The user's username + * * @return ElggUser|false Depending on success */ function get_user_by_username($username) { global $CONFIG, $USERNAME_TO_GUID_MAP_CACHE; + // Fixes #6052. Username is frequently sniffed from the path info, which, + // unlike $_GET, is not URL decoded. If the username was not URL encoded, + // this is harmless. + $username = rawurldecode($username); + $username = sanitise_string($username); $access = get_access_sql_suffix('e'); // Caching - if ( (isset($USERNAME_TO_GUID_MAP_CACHE[$username])) && (retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username])) ) { - return retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]); + if ((isset($USERNAME_TO_GUID_MAP_CACHE[$username])) + && (_elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]))) { + return _elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]); } - $row = get_data_row("SELECT e.* from {$CONFIG->dbprefix}users_entity u join {$CONFIG->dbprefix}entities e on e.guid=u.guid where u.username='$username' and $access "); - if ($row) { - $USERNAME_TO_GUID_MAP_CACHE[$username] = $row->guid; - return new ElggUser($row); + $query = "SELECT e.* from {$CONFIG->dbprefix}users_entity u + join {$CONFIG->dbprefix}entities e on e.guid=u.guid + where u.username='$username' and $access "; + + $entity = get_data_row($query, 'entity_row_to_elggstar'); + if ($entity) { + $USERNAME_TO_GUID_MAP_CACHE[$username] = $entity->guid; + } else { + $entity = false; } - return false; + return $entity; } /** * Get user by session code * * @param string $code The session code + * * @return ElggUser|false Depending on success */ function get_user_by_code($code) { @@ -907,24 +596,30 @@ function get_user_by_code($code) { $access = get_access_sql_suffix('e'); // Caching - if ( (isset($CODE_TO_GUID_MAP_CACHE[$code])) && (retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code])) ) { - return retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]); + if ((isset($CODE_TO_GUID_MAP_CACHE[$code])) + && (_elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]))) { + + return _elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]); } - $row = get_data_row("SELECT e.* from {$CONFIG->dbprefix}users_entity u join {$CONFIG->dbprefix}entities e on e.guid=u.guid where u.code='$code' and $access"); - if ($row) { - $CODE_TO_GUID_MAP_CACHE[$code] = $row->guid; - return new ElggUser($row); + $query = "SELECT e.* from {$CONFIG->dbprefix}users_entity u + join {$CONFIG->dbprefix}entities e on e.guid=u.guid + where u.code='$code' and $access"; + + $entity = get_data_row($query, 'entity_row_to_elggstar'); + if ($entity) { + $CODE_TO_GUID_MAP_CACHE[$code] = $entity->guid; } - return false; + return $entity; } /** - * Get an array of users from their + * Get an array of users from an email address * * @param string $email Email address. - * @return Array of users + * + * @return array */ function get_user_by_email($email) { global $CONFIG; @@ -933,125 +628,72 @@ function get_user_by_email($email) { $access = get_access_sql_suffix('e'); - $query = "SELECT e.* from {$CONFIG->dbprefix}entities e join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid where email='$email' and $access"; + $query = "SELECT e.* from {$CONFIG->dbprefix}entities e + join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid + where email='$email' and $access"; return get_data($query, 'entity_row_to_elggstar'); } /** - * Searches for a user based on a complete or partial name or username. - * - * @param string $criteria The partial or full name or username. - * @param int $limit Limit of the search. - * @param int $offset Offset. - * @param string $order_by The order. - * @param boolean $count Whether to return the count of results or just the results. - * @deprecated 1.7 - */ -function search_for_user($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { - elgg_deprecated_notice('search_for_user() was deprecated by new search.', 1.7); - global $CONFIG; - - $criteria = sanitise_string($criteria); - $limit = (int)$limit; - $offset = (int)$offset; - $order_by = sanitise_string($order_by); - - $access = get_access_sql_suffix("e"); - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT e.* "; - } - $query .= "from {$CONFIG->dbprefix}entities e join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid where "; - // $query .= " match(u.name,u.username) against ('$criteria') "; - $query .= "(u.name like \"%{$criteria}%\" or u.username like \"%{$criteria}%\")"; - $query .= " and $access"; - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } - } - return false; -} - -/** - * Displays a list of user objects that have been searched for. - * - * @see elgg_view_entity_list - * - * @param string $tag Search criteria - * @param int $limit The number of entities to display on a page - * @return string The list in a form suitable to display - * @deprecated 1.7 - */ -function list_user_search($tag, $limit = 10) { - elgg_deprecated_notice('list_user_search() deprecated by new search', 1.7); - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = (int) search_for_user($tag, 10, 0, '', true); - $entities = search_for_user($tag, $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); -} - -/** * A function that returns a maximum of $limit users who have done something within the last - * $seconds seconds. + * $seconds seconds or the total count of active users. + * + * @param int $seconds Number of seconds (default 600 = 10min) + * @param int $limit Limit, default 10. + * @param int $offset Offset, default 0. + * @param bool $count Count, default false. * - * @param int $seconds Number of seconds (default 600 = 10min) - * @param int $limit Limit, default 10. - * @param int $offset Offset, defualt 0. + * @return mixed */ -function find_active_users($seconds = 600, $limit = 10, $offset = 0) { - global $CONFIG; - +function find_active_users($seconds = 600, $limit = 10, $offset = 0, $count = false) { $seconds = (int)$seconds; $limit = (int)$limit; $offset = (int)$offset; + $params = array('seconds' => $seconds, 'limit' => $limit, 'offset' => $offset, 'count' => $count); + $data = elgg_trigger_plugin_hook('find_active_users', 'system', $params, NULL); + if (!$data) { + global $CONFIG; - $time = time() - $seconds; + $time = time() - $seconds; - $access = get_access_sql_suffix("e"); - - $query = "SELECT distinct e.* from {$CONFIG->dbprefix}entities e join {$CONFIG->dbprefix}users_entity u on e.guid = u.guid where u.last_action >= {$time} and $access order by u.last_action desc limit {$offset},{$limit}"; - - return get_data($query, "entity_row_to_elggstar"); + $data = elgg_get_entities(array( + 'type' => 'user', + 'limit' => $limit, + 'offset' => $offset, + 'count' => $count, + 'joins' => array("join {$CONFIG->dbprefix}users_entity u on e.guid = u.guid"), + 'wheres' => array("u.last_action >= {$time}"), + 'order_by' => "u.last_action desc" + )); + } + return $data; } /** * Generate and send a password request email to a given user's registered email address. * - * @param int $user_guid + * @param int $user_guid User GUID + * + * @return bool */ function send_new_password_request($user_guid) { - global $CONFIG; - $user_guid = (int)$user_guid; $user = get_entity($user_guid); - if ($user) { + if ($user instanceof ElggUser) { // generate code $code = generate_random_cleartext_password(); - //create_metadata($user_guid, 'conf_code', $code,'', 0, ACCESS_PRIVATE); - set_private_setting($user_guid, 'passwd_conf_code', $code); + $user->setPrivateSetting('passwd_conf_code', $code); // generate link - $link = $CONFIG->site->url . "pg/resetpassword?u=$user_guid&c=$code"; + $link = elgg_get_site_url() . "resetpassword?u=$user_guid&c=$code"; // generate email - $email = sprintf(elgg_echo('email:resetreq:body'), $user->name, $_SERVER['REMOTE_ADDR'], $link); + $email = elgg_echo('email:resetreq:body', array($user->name, $_SERVER['REMOTE_ADDR'], $link)); - return notify_user($user->guid, $CONFIG->site->guid, elgg_echo('email:resetreq:subject'), $email, NULL, 'email'); + return notify_user($user->guid, elgg_get_site_entity()->guid, + elgg_echo('email:resetreq:subject'), $email, array(), 'email'); } return false; @@ -1062,23 +704,24 @@ function send_new_password_request($user_guid) { * * This can only be called from execute_new_password_request(). * - * @param int $user_guid The user. - * @param string $password password text (which will then be converted into a hash and stored) + * @param int $user_guid The user. + * @param string $password Text (which will then be converted into a hash and stored) + * + * @return bool */ function force_user_password_reset($user_guid, $password) { - global $CONFIG; - - if (call_gatekeeper('execute_new_password_request', __FILE__)) { - $user = get_entity($user_guid); + $user = get_entity($user_guid); + if ($user instanceof ElggUser) { + $ia = elgg_set_ignore_access(); - if ($user) { - $salt = generate_random_cleartext_password(); // Reset the salt - $user->salt = $salt; + $user->salt = generate_random_cleartext_password(); + $hash = generate_user_password($user, $password); + $user->password = $hash; + $result = (bool)$user->save(); - $hash = generate_user_password($user, $password); + elgg_set_ignore_access($ia); - return update_data("UPDATE {$CONFIG->dbprefix}users_entity set password='$hash', salt='$salt' where guid=$user_guid"); - } + return $result; } return false; @@ -1087,8 +730,10 @@ function force_user_password_reset($user_guid, $password) { /** * Validate and execute a password reset for a user. * - * @param int $user_guid The user id + * @param int $user_guid The user id * @param string $conf_code Confirmation code as sent in the request email. + * + * @return mixed */ function execute_new_password_request($user_guid, $conf_code) { global $CONFIG; @@ -1096,17 +741,22 @@ function execute_new_password_request($user_guid, $conf_code) { $user_guid = (int)$user_guid; $user = get_entity($user_guid); - $saved_code = get_private_setting($user_guid, 'passwd_conf_code'); + if ($user instanceof ElggUser) { + $saved_code = $user->getPrivateSetting('passwd_conf_code'); - if ($user && $saved_code && $saved_code == $conf_code) { - $password = generate_random_cleartext_password(); + if ($saved_code && $saved_code == $conf_code) { + $password = generate_random_cleartext_password(); - if (force_user_password_reset($user_guid, $password)) { - remove_private_setting($user_guid, 'passwd_conf_code'); + if (force_user_password_reset($user_guid, $password)) { + remove_private_setting($user_guid, 'passwd_conf_code'); + // clean the logins failures + reset_login_failure_count($user_guid); + + $email = elgg_echo('email:resetpassword:body', array($user->name, $password)); - $email = sprintf(elgg_echo('email:resetpassword:body'), $user->name, $password); - - return notify_user($user->guid, $CONFIG->site->guid, elgg_echo('email:resetpassword:subject'), $email, NULL, 'email'); + return notify_user($user->guid, $CONFIG->site->guid, + elgg_echo('email:resetpassword:subject'), $email, array(), 'email'); + } } } @@ -1114,130 +764,11 @@ function execute_new_password_request($user_guid, $conf_code) { } /** - * Handles pages for password reset requests. - * - * @param unknown_type $page - * @return unknown_type - */ -function elgg_user_resetpassword_page_handler($page) { - global $CONFIG; - - $user_guid = get_input('u'); - $code = get_input('c'); - - $user = get_entity($user_guid); - - // don't check code here to avoid automated attacks - if (!$user instanceof ElggUser) { - register_error(elgg_echo('user:passwordreset:unknown_user')); - forward(); - } - - $form_body = elgg_echo('user:resetpassword:reset_password_confirm') . "<br />"; - - $form_body .= elgg_view('input/hidden', array( - 'internalname' => 'u', - 'value' => $user_guid - )); - - $form_body .= elgg_view('input/hidden', array( - 'internalname' => 'c', - 'value' => $code - )); - - $form_body .= elgg_view('input/submit', array( - 'value' => elgg_echo('resetpassword') - )); - - $form .= elgg_view('input/form', array( - 'body' => $form_body, - 'action' => $CONFIG->site->url . 'action/user/passwordreset' - )); - - $content = elgg_view_title(elgg_echo('resetpassword')); - $content .= elgg_view('page_elements/contentwrapper', array('body' => $form)); - - page_draw($title, $content); -} - -/** - * Set the validation status for a user. - * - * @param bool $status Validated (true) or false - * @param string $method Optional method to say how a user was validated - * @return bool - */ -function set_user_validation_status($user_guid, $status, $method = '') { - if (!$status) { - $method = ''; - } - - if ($status) { - if ( - (create_metadata($user_guid, 'validated', $status,'', 0, ACCESS_PUBLIC)) && - (create_metadata($user_guid, 'validated_method', $method,'', 0, ACCESS_PUBLIC)) - ) { - return true; - } - } else { - $validated = get_metadata_byname($user_guid, 'validated'); - $validated_method = get_metadata_byname($user_guid, 'validated_method'); - - if ( - ($validated) && - ($validated_method) && - (delete_metadata($validated->id)) && - (delete_metadata($validated_method->id)) - ) - return true; - } - - return false; -} - -/** - * Trigger an event requesting that a user guid be validated somehow - either by email address or some other way. - * - * This event invalidates any existing values and returns - * - * @param unknown_type $user_guid - */ -function request_user_validation($user_guid) { - $user = get_entity($user_guid); - - if (($user) && ($user instanceof ElggUser)) { - // invalidate any existing validations - set_user_validation_status($user_guid, false); - - // request validation - trigger_elgg_event('validate', 'user', $user); - } -} - -/** - * Validates an email address. - * - * @param string $address Email address. - * @return bool - */ -function is_email_address($address) { - // TODO: Make this better! - - if (strpos($address, '@')=== false) { - return false; - } - - if (strpos($address, '.')=== false) { - return false; - } - - return true; -} - -/** - * Simple function that will generate a random clear text password suitable for feeding into generate_user_password(). + * Simple function that will generate a random clear text password + * suitable for feeding into generate_user_password(). * * @see generate_user_password + * * @return string */ function generate_random_cleartext_password() { @@ -1247,10 +778,10 @@ function generate_random_cleartext_password() { /** * Generate a password for a user, currently uses MD5. * - * Later may introduce salting etc. + * @param ElggUser $user The user this is being generated for. + * @param string $password Password in clear text * - * @param ElggUser $user The user this is being generated for. - * @param string $password Password in clear text + * @return string */ function generate_user_password(ElggUser $user, $password) { return md5($password . $user->salt); @@ -1261,7 +792,9 @@ function generate_user_password(ElggUser $user, $password) { * * This should only permit chars that are valid on the file system as well. * - * @param string $username + * @param string $username Username + * + * @return bool * @throws RegistrationException on invalid */ function validate_username($username) { @@ -1273,57 +806,80 @@ function validate_username($username) { } if (strlen($username) < $CONFIG->minusername) { - throw new RegistrationException(elgg_echo('registration:usernametooshort')); + $msg = elgg_echo('registration:usernametooshort', array($CONFIG->minusername)); + throw new RegistrationException($msg); + } + + // username in the database has a limit of 128 characters + if (strlen($username) > 128) { + $msg = elgg_echo('registration:usernametoolong', array(128)); + throw new RegistrationException($msg); } // Blacklist for bad characters (partially nicked from mediawiki) - $blacklist = '/[' . - '\x{0080}-\x{009f}' . # iso-8859-1 control chars - '\x{00a0}' . # non-breaking space - '\x{2000}-\x{200f}' . # various whitespace - '\x{2028}-\x{202f}' . # breaks and control chars - '\x{3000}' . # ideographic space - '\x{e000}-\x{f8ff}' . # private use + '\x{0080}-\x{009f}' . // iso-8859-1 control chars + '\x{00a0}' . // non-breaking space + '\x{2000}-\x{200f}' . // various whitespace + '\x{2028}-\x{202f}' . // breaks and control chars + '\x{3000}' . // ideographic space + '\x{e000}-\x{f8ff}' . // private use ']/u'; if ( preg_match($blacklist, $username) ) { + // @todo error message needs work throw new RegistrationException(elgg_echo('registration:invalidchars')); } - // Belts and braces TODO: Tidy into main unicode - $blacklist2 = '/\\"\'*& ?#%^(){}[]~?<>;|¬`@-+='; - for ($n=0; $n < strlen($blacklist2); $n++) { - if (strpos($username, $blacklist2[$n])!==false) { - throw new RegistrationException(elgg_echo('registration:invalidchars')); + // Belts and braces + // @todo Tidy into main unicode + $blacklist2 = '\'/\\"*& ?#%^(){}[]~?<>;|¬`@-+='; + + for ($n = 0; $n < strlen($blacklist2); $n++) { + if (strpos($username, $blacklist2[$n]) !== false) { + $msg = elgg_echo('registration:invalidchars', array($blacklist2[$n], $blacklist2)); + $msg = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8'); + throw new RegistrationException($msg); } } $result = true; - return trigger_plugin_hook('registeruser:validate:username', 'all', array('username' => $username), $result); + return elgg_trigger_plugin_hook('registeruser:validate:username', 'all', + array('username' => $username), $result); } /** * Simple validation of a password. * - * @param string $password + * @param string $password Clear text password + * + * @return bool * @throws RegistrationException on invalid */ function validate_password($password) { - if (strlen($password) < 6) { - throw new RegistrationException(elgg_echo('registration:passwordtooshort')); + global $CONFIG; + + if (!isset($CONFIG->min_password_length)) { + $CONFIG->min_password_length = 6; + } + + if (strlen($password) < $CONFIG->min_password_length) { + $msg = elgg_echo('registration:passwordtooshort', array($CONFIG->min_password_length)); + throw new RegistrationException($msg); } $result = true; - return trigger_plugin_hook('registeruser:validate:password', 'all', array('password' => $password), $result); + return elgg_trigger_plugin_hook('registeruser:validate:password', 'all', + array('password' => $password), $result); } /** * Simple validation of a email. * - * @param string $address + * @param string $address Email address + * * @throws RegistrationException on invalid * @return bool */ @@ -1334,28 +890,31 @@ function validate_email_address($address) { // Got here, so lets try a hook (defaulting to ok) $result = true; - return trigger_plugin_hook('registeruser:validate:email', 'all', array('email' => $address), $result); + return elgg_trigger_plugin_hook('registeruser:validate:email', 'all', + array('email' => $address), $result); } /** * Registers a user, returning false if the username already exists * - * @param string $username The username of the new user - * @param string $password The password - * @param string $name The user's display name - * @param string $email Their email address - * @param bool $allow_multiple_emails Allow the same email address to be registered multiple times? - * @param int $friend_guid Optionally, GUID of a user this user will friend once fully registered + * @param string $username The username of the new user + * @param string $password The password + * @param string $name The user's display name + * @param string $email Their email address + * @param bool $allow_multiple_emails Allow the same email address to be + * registered multiple times? + * @param int $friend_guid GUID of a user to friend once fully registered + * @param string $invitecode An invite code from a friend + * * @return int|false The new user's GUID; false on failure + * @throws RegistrationException */ -function register_user($username, $password, $name, $email, $allow_multiple_emails = false, $friend_guid = 0, $invitecode = '') { - // Load the configuration - global $CONFIG; +function register_user($username, $password, $name, $email, +$allow_multiple_emails = false, $friend_guid = 0, $invitecode = '') { - $username = trim($username); // no need to trim password. - $password = $password; - $name = trim($name); + $username = trim($username); + $name = trim(strip_tags($name)); $email = trim($email); // A little sanity checking @@ -1366,43 +925,33 @@ function register_user($username, $password, $name, $email, $allow_multiple_emai return false; } - // See if it exists and is disabled + // Make sure a user with conflicting details hasn't registered and been disabled $access_status = access_get_show_hidden_status(); access_show_hidden_entities(true); - // Validate email address if (!validate_email_address($email)) { throw new RegistrationException(elgg_echo('registration:emailnotvalid')); } - // Validate password if (!validate_password($password)) { throw new RegistrationException(elgg_echo('registration:passwordnotvalid')); } - // Validate the username if (!validate_username($username)) { throw new RegistrationException(elgg_echo('registration:usernamenotvalid')); } - // Check to see if $username exists already if ($user = get_user_by_username($username)) { - //return false; throw new RegistrationException(elgg_echo('registration:userexists')); } - // If we're not allowed multiple emails then see if this address has been used before if ((!$allow_multiple_emails) && (get_user_by_email($email))) { throw new RegistrationException(elgg_echo('registration:dupeemail')); } access_show_hidden_entities($access_status); - // Check to see if we've registered the first admin yet. - // If not, this is the first admin user! - $have_admin = datalist_get('admin_registered'); - - // Otherwise ... + // Create user $user = new ElggUser(); $user->username = $username; $user->email = $email; @@ -1412,6 +961,7 @@ function register_user($username, $password, $name, $email, $allow_multiple_emai $user->password = generate_user_password($user, $password); $user->owner_guid = 0; // Users aren't owned by anyone, even if they are admin created. $user->container_guid = 0; // Users aren't contained by anyone, even if they are admin created. + $user->language = get_current_language(); $user->save(); // If $friend_guid has been set, make mutual friends @@ -1422,22 +972,12 @@ function register_user($username, $password, $name, $email, $allow_multiple_emai $friend_user->addFriend($user->guid); // @todo Should this be in addFriend? - add_to_river('friends/river/create', 'friend', $user->getGUID(), $friend_guid); - add_to_river('friends/river/create', 'friend', $friend_guid, $user->getGUID()); + add_to_river('river/relationship/friend/create', 'friend', $user->getGUID(), $friend_guid); + add_to_river('river/relationship/friend/create', 'friend', $friend_guid, $user->getGUID()); } } } - global $registering_admin; - if (!$have_admin) { - $user->admin = true; - set_user_validation_status($user->getGUID(), TRUE, 'first_run'); - datalist_set('admin_registered', 1); - $registering_admin = true; - } else { - $registering_admin = false; - } - // Turn on email notifications by default set_user_notification_setting($user->getGUID(), 'email', true); @@ -1448,6 +988,7 @@ function register_user($username, $password, $name, $email, $allow_multiple_emai * Generates a unique invite code for a user * * @param string $username The username of the user sending the invitation + * * @return string Invite code */ function generate_invite_code($username) { @@ -1456,269 +997,607 @@ function generate_invite_code($username) { } /** - * Adds collection submenu items + * Set the validation status for a user. * + * @param int $user_guid The user's GUID + * @param bool $status Validated (true) or unvalidated (false) + * @param string $method Optional method to say how a user was validated + * @return bool + * @since 1.8.0 */ -function collections_submenu_items() { - global $CONFIG; - $user = get_loggedin_user(); - add_submenu_item(elgg_echo('friends:collections'), $CONFIG->wwwroot . "pg/collections/" . $user->username); - add_submenu_item(elgg_echo('friends:collections:add'),$CONFIG->wwwroot."pg/collections/add"); +function elgg_set_user_validation_status($user_guid, $status, $method = '') { + $result1 = create_metadata($user_guid, 'validated', $status, '', 0, ACCESS_PUBLIC, false); + $result2 = create_metadata($user_guid, 'validated_method', $method, '', 0, ACCESS_PUBLIC, false); + if ($result1 && $result2) { + return true; + } else { + return false; + } } /** - * Page handler for friends + * Gets the validation status of a user. * + * @param int $user_guid The user's GUID + * @return bool|null Null means status was not set for this user. + * @since 1.8.0 */ -function friends_page_handler($page_elements) { - if (isset($page_elements[0]) && $user = get_user_by_username($page_elements[0])) { - set_page_owner($user->getGUID()); +function elgg_get_user_validation_status($user_guid) { + $md = elgg_get_metadata(array( + 'guid' => $user_guid, + 'metadata_name' => 'validated' + )); + if ($md == false) { + return null; } - if ($_SESSION['guid'] == page_owner()) { - collections_submenu_items(); + + if ($md[0]->value) { + return true; } - require_once(dirname(dirname(dirname(__FILE__))) . "/friends/index.php"); + return false; +} + +/** + * Adds collection submenu items + * + * @return void + * @access private + */ +function collections_submenu_items() { + + $user = elgg_get_logged_in_user_entity(); + + elgg_register_menu_item('page', array( + 'name' => 'friends:view:collections', + 'text' => elgg_echo('friends:collections'), + 'href' => "collections/$user->username", + )); } /** - * Page handler for friends of + * Page handler for friends-related pages * + * @param array $segments URL segments + * @param string $handler The first segment in URL used for routing + * + * @return bool + * @access private */ -function friends_of_page_handler($page_elements) { - if (isset($page_elements[0]) && $user = get_user_by_username($page_elements[0])) { - set_page_owner($user->getGUID()); +function friends_page_handler($segments, $handler) { + elgg_set_context('friends'); + + if (isset($segments[0]) && $user = get_user_by_username($segments[0])) { + elgg_set_page_owner_guid($user->getGUID()); } - if ($_SESSION['guid'] == page_owner()) { + if (elgg_get_logged_in_user_guid() == elgg_get_page_owner_guid()) { collections_submenu_items(); } - require_once(dirname(dirname(dirname(__FILE__))) . "/friends/of.php"); + + switch ($handler) { + case 'friends': + require_once(dirname(dirname(dirname(__FILE__))) . "/pages/friends/index.php"); + break; + case 'friendsof': + require_once(dirname(dirname(dirname(__FILE__))) . "/pages/friends/of.php"); + break; + default: + return false; + } + return true; } /** - * Page handler for friends of + * Page handler for friends collections * + * @param array $page_elements Page elements + * + * @return bool + * @access private */ function collections_page_handler($page_elements) { + gatekeeper(); + elgg_set_context('friends'); + $base = elgg_get_config('path'); if (isset($page_elements[0])) { if ($page_elements[0] == "add") { - set_page_owner($_SESSION['guid']); + elgg_set_page_owner_guid(elgg_get_logged_in_user_guid()); collections_submenu_items(); - require_once(dirname(dirname(dirname(__FILE__))) . "/friends/add.php"); + require_once "{$base}pages/friends/collections/add.php"; + return true; } else { - if ($user = get_user_by_username($page_elements[0])) { - set_page_owner($user->getGUID()); - if ($_SESSION['guid'] == page_owner()) { + $user = get_user_by_username($page_elements[0]); + if ($user) { + elgg_set_page_owner_guid($user->getGUID()); + if (elgg_get_logged_in_user_guid() == elgg_get_page_owner_guid()) { collections_submenu_items(); } - require_once(dirname(dirname(dirname(__FILE__))) . "/friends/collections.php"); + require_once "{$base}pages/friends/collections/view.php"; + return true; } } } + return false; } /** - * Page handler for dashboard - */ -function dashboard_page_handler($page_elements) { - require_once(dirname(dirname(dirname(__FILE__))) . "/dashboard/index.php"); -} - - -/** - * Page handler for registration + * Page handler for account related pages + * + * @param array $page_elements Page elements + * @param string $handler The handler string + * + * @return bool + * @access private */ -function registration_page_handler($page_elements) { - require_once(dirname(dirname(dirname(__FILE__))) . "/account/register.php"); +function elgg_user_account_page_handler($page_elements, $handler) { + + $base_dir = elgg_get_root_path() . 'pages/account'; + switch ($handler) { + case 'login': + require_once("$base_dir/login.php"); + break; + case 'forgotpassword': + require_once("$base_dir/forgotten_password.php"); + break; + case 'resetpassword': + require_once("$base_dir/reset_password.php"); + break; + case 'register': + require_once("$base_dir/register.php"); + break; + default: + return false; + } + return true; } /** * Sets the last action time of the given user to right now. * * @param int $user_guid The user GUID + * + * @return void */ function set_last_action($user_guid) { $user_guid = (int) $user_guid; global $CONFIG; $time = time(); - execute_delayed_write_query("UPDATE {$CONFIG->dbprefix}users_entity set prev_last_action = last_action, last_action = {$time} where guid = {$user_guid}"); + $query = "UPDATE {$CONFIG->dbprefix}users_entity + set prev_last_action = last_action, + last_action = {$time} where guid = {$user_guid}"; + + execute_delayed_write_query($query); } /** * Sets the last logon time of the given user to right now. * * @param int $user_guid The user GUID + * + * @return void */ function set_last_login($user_guid) { $user_guid = (int) $user_guid; global $CONFIG; $time = time(); - execute_delayed_write_query("UPDATE {$CONFIG->dbprefix}users_entity set prev_last_login = last_login, last_login = {$time} where guid = {$user_guid}"); + $query = "UPDATE {$CONFIG->dbprefix}users_entity + set prev_last_login = last_login, last_login = {$time} where guid = {$user_guid}"; + + execute_delayed_write_query($query); } /** - * A permissions plugin hook that grants access to users if they are newly created - allows - * for email activation. + * Creates a relationship between this site and the user. * - * TODO: Do this in a better way! + * @param string $event create + * @param string $object_type user + * @param ElggUser $object User object * - * @param unknown_type $hook - * @param unknown_type $entity_type - * @param unknown_type $returnvalue - * @param unknown_type $params + * @return void + * @access private */ -function new_user_enable_permissions_check($hook, $entity_type, $returnvalue, $params) { - $entity = $params['entity']; - $user = $params['user']; - if (($entity) && ($entity instanceof ElggUser)) { - if ( - (($entity->disable_reason == 'new_user') || ( - // if this isn't set at all they're a "new user" - !$entity->validated - )) - && (!isloggedin())) { - return true; +function user_create_hook_add_site_relationship($event, $object_type, $object) { + add_entity_relationship($object->getGUID(), 'member_of_site', elgg_get_site_entity()->guid); +} + +/** + * Serves the user's avatar + * + * @param string $hook + * @param string $entity_type + * @param string $returnvalue + * @param array $params + * @return string + * @access private + */ +function user_avatar_hook($hook, $entity_type, $returnvalue, $params) { + $user = $params['entity']; + $size = $params['size']; + + if (isset($user->icontime)) { + return "avatar/view/$user->username/$size/$user->icontime"; + } else { + return "_graphics/icons/user/default{$size}.gif"; + } +} + +/** + * Setup the default user hover menu + * @access private + */ +function elgg_user_hover_menu($hook, $type, $return, $params) { + $user = $params['entity']; + /* @var ElggUser $user */ + + if (elgg_is_logged_in()) { + if (elgg_get_logged_in_user_guid() != $user->guid) { + if ($user->isFriend()) { + $url = "action/friends/remove?friend={$user->guid}"; + $text = elgg_echo('friend:remove'); + $name = 'remove_friend'; + } else { + $url = "action/friends/add?friend={$user->guid}"; + $text = elgg_echo('friend:add'); + $name = 'add_friend'; + } + $url = elgg_add_action_tokens_to_url($url); + $item = new ElggMenuItem($name, $text, $url); + $item->setSection('action'); + $return[] = $item; + } else { + $url = "profile/$user->username/edit"; + $item = new ElggMenuItem('profile:edit', elgg_echo('profile:edit'), $url); + $item->setSection('action'); + $return[] = $item; + + $url = "avatar/edit/$user->username"; + $item = new ElggMenuItem('avatar:edit', elgg_echo('avatar:edit'), $url); + $item->setSection('action'); + $return[] = $item; + } + } + + // prevent admins from banning or deleting themselves + if (elgg_get_logged_in_user_guid() == $user->guid) { + return $return; + } + + if (elgg_is_admin_logged_in()) { + $actions = array(); + if (!$user->isBanned()) { + $actions[] = 'ban'; + } else { + $actions[] = 'unban'; + } + $actions[] = 'delete'; + $actions[] = 'resetpassword'; + if (!$user->isAdmin()) { + $actions[] = 'makeadmin'; + } else { + $actions[] = 'removeadmin'; + } + + foreach ($actions as $action) { + $url = "action/admin/user/$action?guid={$user->guid}"; + $url = elgg_add_action_tokens_to_url($url); + $item = new ElggMenuItem($action, elgg_echo($action), $url); + $item->setSection('admin'); + $item->setLinkClass('elgg-requires-confirmation'); + + $return[] = $item; } + + $url = "profile/$user->username/edit"; + $item = new ElggMenuItem('profile:edit', elgg_echo('profile:edit'), $url); + $item->setSection('admin'); + $return[] = $item; + + $url = "settings/user/$user->username"; + $item = new ElggMenuItem('settings:edit', elgg_echo('settings:edit'), $url); + $item->setSection('admin'); + $return[] = $item; } - return $returnvalue; + return $return; } /** - * Creates a relationship between this site and the user. + * Setup the menu shown with an entity * - * @param $event - * @param $object_type - * @param $object - * @return bool + * @param string $hook + * @param string $type + * @param array $return + * @param array $params + * @return array + * + * @access private */ -function user_create_hook_add_site_relationship($event, $object_type, $object) { - global $CONFIG; +function elgg_users_setup_entity_menu($hook, $type, $return, $params) { + if (elgg_in_context('widgets')) { + return $return; + } + + $entity = $params['entity']; + if (!elgg_instanceof($entity, 'user')) { + return $return; + } + /* @var ElggUser $entity */ + + if ($entity->isBanned()) { + $banned = elgg_echo('banned'); + $options = array( + 'name' => 'banned', + 'text' => "<span>$banned</span>", + 'href' => false, + 'priority' => 0, + ); + $return = array(ElggMenuItem::factory($options)); + } else { + $return = array(); + if (isset($entity->location)) { + $location = htmlspecialchars($entity->location, ENT_QUOTES, 'UTF-8', false); + $options = array( + 'name' => 'location', + 'text' => "<span>$location</span>", + 'href' => false, + 'priority' => 150, + ); + $return[] = ElggMenuItem::factory($options); + } + } - add_entity_relationship($object->getGUID(), 'member_of_site', $CONFIG->site->getGUID()); + return $return; } /** - * Sets up user-related menu items + * This function loads a set of default fields into the profile, then triggers a hook letting other plugins to edit + * add and delete fields. * + * Note: This is a secondary system:init call and is run at a super low priority to guarantee that it is called after all + * other plugins have initialised. + * @access private */ -function users_pagesetup() { - // Load config +function elgg_profile_fields_setup() { global $CONFIG; - //add submenu options - if (get_context() == "friends" || get_context() == "friendsof" || get_context() == "collections") { - add_submenu_item(elgg_echo('friends'),$CONFIG->wwwroot."pg/friends/" . page_owner_entity()->username); - add_submenu_item(elgg_echo('friends:of'),$CONFIG->wwwroot."pg/friendsof/" . page_owner_entity()->username); + $profile_defaults = array ( + 'description' => 'longtext', + 'briefdescription' => 'text', + 'location' => 'location', + 'interests' => 'tags', + 'skills' => 'tags', + 'contactemail' => 'email', + 'phone' => 'text', + 'mobile' => 'text', + 'website' => 'url', + 'twitter' => 'text' + ); + + $loaded_defaults = array(); + if ($fieldlist = elgg_get_config('profile_custom_fields')) { + if (!empty($fieldlist)) { + $fieldlistarray = explode(',', $fieldlist); + foreach ($fieldlistarray as $listitem) { + if ($translation = elgg_get_config("admin_defined_profile_{$listitem}")) { + $type = elgg_get_config("admin_defined_profile_type_{$listitem}"); + $loaded_defaults["admin_defined_profile_{$listitem}"] = $type; + add_translation(get_current_language(), array("profile:admin_defined_profile_{$listitem}" => $translation)); + } + } + } + } + + if (count($loaded_defaults)) { + $CONFIG->profile_using_custom = true; + $profile_defaults = $loaded_defaults; + } + + $CONFIG->profile_fields = elgg_trigger_plugin_hook('profile:fields', 'profile', NULL, $profile_defaults); + + // register any tag metadata names + foreach ($CONFIG->profile_fields as $name => $type) { + if ($type == 'tags' || $type == 'location' || $type == 'tag') { + elgg_register_tag_metadata_name($name); + // register a tag name translation + add_translation(get_current_language(), array("tag_names:$name" => elgg_echo("profile:$name"))); + } } } /** - * Users initialisation function, which establishes the page handler + * Avatar page handler * + * /avatar/edit/<username> + * /avatar/view/<username>/<size>/<icontime> + * + * @param array $page + * @return bool + * @access private */ -function users_init() { - // Load config +function elgg_avatar_page_handler($page) { global $CONFIG; - // Set up menu for logged in users - if (isloggedin()) { - $user = get_loggedin_user(); - add_menu(elgg_echo('friends'), $CONFIG->wwwroot . "pg/friends/" . $user->username); + $user = get_user_by_username($page[1]); + if ($user) { + elgg_set_page_owner_guid($user->getGUID()); } - register_page_handler('friends', 'friends_page_handler'); - register_page_handler('friendsof', 'friends_of_page_handler'); - register_page_handler('collections', 'collections_page_handler'); - register_page_handler('dashboard', 'dashboard_page_handler'); - register_page_handler('register', 'registration_page_handler'); - register_page_handler('resetpassword', 'elgg_user_resetpassword_page_handler'); - - register_action("register", true); - register_action("useradd", true); - register_action("friends/add"); - register_action("friends/remove"); - register_action('friends/addcollection'); - register_action('friends/deletecollection'); - register_action('friends/editcollection'); - register_action("user/spotlight"); - - register_action("usersettings/save"); - - register_action("user/passwordreset"); - register_action("user/requestnewpassword"); + if ($page[0] == 'edit') { + require_once("{$CONFIG->path}pages/avatar/edit.php"); + return true; + } else { + set_input('size', $page[2]); + require_once("{$CONFIG->path}pages/avatar/view.php"); + return true; + } + return false; +} - // User name change - extend_elgg_settings_page('user/settings/name', 'usersettings/user', 1); - //register_action("user/name"); +/** + * Profile page handler + * + * @param array $page + * @return bool + * @access private + */ +function elgg_profile_page_handler($page) { + global $CONFIG; - // User password change - extend_elgg_settings_page('user/settings/password', 'usersettings/user', 1); - //register_action("user/password"); + $user = get_user_by_username($page[0]); + elgg_set_page_owner_guid($user->guid); - // Add email settings - extend_elgg_settings_page('user/settings/email', 'usersettings/user', 1); - //register_action("email/save"); + if ($page[1] == 'edit') { + require_once("{$CONFIG->path}pages/profile/edit.php"); + return true; + } + return false; +} - // Add language settings - extend_elgg_settings_page('user/settings/language', 'usersettings/user', 1); +/** + * Sets up user-related menu items + * + * @return void + * @access private + */ +function users_pagesetup() { - // Add default access settings - extend_elgg_settings_page('user/settings/default_access', 'usersettings/user', 1); + $owner = elgg_get_page_owner_entity(); + $viewer = elgg_get_logged_in_user_entity(); + + if ($owner) { + $params = array( + 'name' => 'friends', + 'text' => elgg_echo('friends'), + 'href' => 'friends/' . $owner->username, + 'contexts' => array('friends') + ); + elgg_register_menu_item('page', $params); + + $params = array( + 'name' => 'friends:of', + 'text' => elgg_echo('friends:of'), + 'href' => 'friendsof/' . $owner->username, + 'contexts' => array('friends') + ); + elgg_register_menu_item('page', $params); + + elgg_register_menu_item('page', array( + 'name' => 'edit_avatar', + 'href' => "avatar/edit/{$owner->username}", + 'text' => elgg_echo('avatar:edit'), + 'contexts' => array('profile_edit'), + )); - //register_action("user/language"); + elgg_register_menu_item('page', array( + 'name' => 'edit_profile', + 'href' => "profile/{$owner->username}/edit", + 'text' => elgg_echo('profile:edit'), + 'contexts' => array('profile_edit'), + )); + } - // Register the user type - register_entity_type('user',''); + // topbar + if ($viewer) { + elgg_register_menu_item('topbar', array( + 'name' => 'profile', + 'href' => $viewer->getURL(), + 'text' => elgg_view('output/img', array( + 'src' => $viewer->getIconURL('topbar'), + 'alt' => $viewer->name, + 'title' => elgg_echo('profile'), + 'class' => 'elgg-border-plain elgg-transition', + )), + 'priority' => 100, + 'link_class' => 'elgg-topbar-avatar', + )); - register_plugin_hook('usersettings:save','user','users_settings_save'); + elgg_register_menu_item('topbar', array( + 'name' => 'friends', + 'href' => "friends/{$viewer->username}", + 'text' => elgg_view_icon('users'), + 'title' => elgg_echo('friends'), + 'priority' => 300, + )); - register_elgg_event_handler('create', 'user', 'user_create_hook_add_site_relationship'); + elgg_register_menu_item('topbar', array( + 'name' => 'usersettings', + 'href' => "settings/user/{$viewer->username}", + 'text' => elgg_view_icon('settings') . elgg_echo('settings'), + 'priority' => 500, + 'section' => 'alt', + )); - // Handle a special case for newly created users when the user is not logged in - // TODO: handle this better! - register_plugin_hook('permissions_check','all','new_user_enable_permissions_check'); + elgg_register_menu_item('topbar', array( + 'name' => 'logout', + 'href' => "action/logout", + 'text' => elgg_echo('logout'), + 'is_action' => TRUE, + 'priority' => 1000, + 'section' => 'alt', + )); + } } /** - * Returns a formatted list of users suitable for injecting into search. - * @deprecated 1.7 + * Users initialisation function, which establishes the page handler + * + * @return void + * @access private */ -function search_list_users_by_name($hook, $user, $returnvalue, $tag) { - elgg_deprecated_notice('search_list_users_by_name() was deprecated by new search', 1.7); - // Change this to set the number of users that display on the search page - $threshold = 4; +function users_init() { - $object = get_input('object'); + elgg_register_page_handler('friends', 'friends_page_handler'); + elgg_register_page_handler('friendsof', 'friends_page_handler'); + elgg_register_page_handler('register', 'elgg_user_account_page_handler'); + elgg_register_page_handler('forgotpassword', 'elgg_user_account_page_handler'); + elgg_register_page_handler('resetpassword', 'elgg_user_account_page_handler'); + elgg_register_page_handler('login', 'elgg_user_account_page_handler'); + elgg_register_page_handler('avatar', 'elgg_avatar_page_handler'); + elgg_register_page_handler('profile', 'elgg_profile_page_handler'); + elgg_register_page_handler('collections', 'collections_page_handler'); - if (!get_input('offset') && (empty($object) || $object == 'user')) { - if ($users = search_for_user($tag,$threshold)) { - $countusers = search_for_user($tag,0,0,"",true); + elgg_register_plugin_hook_handler('register', 'menu:user_hover', 'elgg_user_hover_menu'); - $return = elgg_view('user/search/startblurb',array('count' => $countusers, 'tag' => $tag)); - foreach($users as $user) { - $return .= elgg_view_entity($user); - } - $return .= elgg_view('user/search/finishblurb',array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag)); - return $return; + elgg_register_action('register', '', 'public'); + elgg_register_action('useradd', '', 'admin'); + elgg_register_action('friends/add'); + elgg_register_action('friends/remove'); + elgg_register_action('avatar/upload'); + elgg_register_action('avatar/crop'); + elgg_register_action('avatar/remove'); + elgg_register_action('profile/edit'); - } - } -} + elgg_register_action('friends/collections/add'); + elgg_register_action('friends/collections/delete'); + elgg_register_action('friends/collections/edit'); -function users_settings_save() { - global $CONFIG; - include($CONFIG->path . "actions/user/name.php"); - include($CONFIG->path . "actions/user/password.php"); - include($CONFIG->path . "actions/email/save.php"); - include($CONFIG->path . "actions/user/language.php"); - include($CONFIG->path . "actions/user/default_access.php"); + elgg_register_plugin_hook_handler('entity:icon:url', 'user', 'user_avatar_hook'); + + elgg_register_action('user/passwordreset', '', 'public'); + elgg_register_action('user/requestnewpassword', '', 'public'); + + elgg_register_widget_type('friends', elgg_echo('friends'), elgg_echo('friends:widget:description')); + + // Register the user type + elgg_register_entity_type('user', ''); + + elgg_register_plugin_hook_handler('register', 'menu:entity', 'elgg_users_setup_entity_menu', 501); + + elgg_register_event_handler('create', 'user', 'user_create_hook_add_site_relationship'); } /** * Runs unit tests for ElggObject + * + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params + * + * @return array + * @access private */ function users_test($hook, $type, $value, $params) { global $CONFIG; @@ -1726,7 +1605,7 @@ function users_test($hook, $type, $value, $params) { return $value; } -//register actions ************************************************************* -register_elgg_event_handler('init','system','users_init',0); -register_elgg_event_handler('pagesetup','system','users_pagesetup',0); -register_plugin_hook('unit_test', 'system', 'users_test'); +elgg_register_event_handler('init', 'system', 'users_init', 0); +elgg_register_event_handler('init', 'system', 'elgg_profile_fields_setup', 10000); // Ensure this runs after other plugins +elgg_register_event_handler('pagesetup', 'system', 'users_pagesetup', 0); +elgg_register_plugin_hook_handler('unit_test', 'system', 'users_test'); diff --git a/engine/lib/usersettings.php b/engine/lib/usersettings.php deleted file mode 100644 index baaf41562..000000000 --- a/engine/lib/usersettings.php +++ /dev/null @@ -1,76 +0,0 @@ -<?php -/** - * Elgg user settings functions. - * Functions for adding and manipulating options on the user settings panel. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - -/** - * Register a user settings page with the admin panel. - * This function extends the view "usersettings/main" with the provided view. This view should provide a description - * and either a control or a link to. - * - * Usage: - * - To add a control to the main admin panel then extend usersettings/main - * - To add a control to a new page create a page which renders a view usersettings/subpage (where subpage is your new page - - * nb. some pages already exist that you can extend), extend the main view to point to it, and add controls to your - * new view. - * - * At the moment this is essentially a wrapper around elgg_extend_view(). - * - * @param string $new_settings_view The view associated with the control you're adding - * @param string $view The view to extend, by default this is 'usersettings/main'. - * @param int $priority Optional priority to govern the appearance in the list. - */ -function extend_elgg_settings_page( $new_settings_view, $view = 'usersettings/main', $priority = 500) { - return elgg_extend_view($view, $new_settings_view, $priority); -} - -function usersettings_pagesetup() { - // Get config - global $CONFIG; - - // Menu options - if (get_context() == "settings") { - $user = get_loggedin_user(); - add_submenu_item(elgg_echo('usersettings:user:opt:linktext'),$CONFIG->wwwroot . "pg/settings/user/{$user->username}/"); - add_submenu_item(elgg_echo('usersettings:plugins:opt:linktext'),$CONFIG->wwwroot . "pg/settings/plugins/{$user->username}/"); - add_submenu_item(elgg_echo('usersettings:statistics:opt:linktext'),$CONFIG->wwwroot . "pg/settings/statistics/{$user->username}/"); - } -} - -function usersettings_page_handler($page) { - global $CONFIG; - - $path = $CONFIG->path . "settings/index.php"; - - if ($page[0]) { - switch ($page[0]) { - case 'user' : $path = $CONFIG->path . "settings/user.php"; break; - case 'statistics' : $path = $CONFIG->path . "settings/statistics.php"; break; - case 'plugins' : $path = $CONFIG->path . "settings/plugins.php"; break; - } - } - - if ($page[1]) { - set_input('username', $page[1]); - } - - include($path); -} - -/** - * Initialise the admin page. - */ -function usersettings_init() { - // Page handler - register_page_handler('settings','usersettings_page_handler'); -} - -/// Register init function -register_elgg_event_handler('init','system','usersettings_init'); -register_elgg_event_handler('pagesetup','system','usersettings_pagesetup');
\ No newline at end of file diff --git a/engine/lib/version.php b/engine/lib/version.php deleted file mode 100644 index 5322e5afa..000000000 --- a/engine/lib/version.php +++ /dev/null @@ -1,132 +0,0 @@ -<?php -/** - * Elgg version library. - * Contains code for handling versioning and upgrades. - * - * @package Elgg - * @subpackage Core - * @link http://elgg.org/ - */ - -/** - * Run any php upgrade scripts which are required - * - * @param int $version Version upgrading from. - * @param bool $quiet Suppress errors. Don't use this. - */ -function upgrade_code($version, $quiet = FALSE) { - global $CONFIG; - - // Elgg and its database must be installed to upgrade it! - if (!is_db_installed() || !is_installed()) { - return FALSE; - } - - $version = (int) $version; - - if ($handle = opendir($CONFIG->path . 'engine/lib/upgrades/')) { - $upgrades = array(); - - while ($updatefile = readdir($handle)) { - // Look for upgrades and add to upgrades list - if (!is_dir($CONFIG->path . 'engine/lib/upgrades/' . $updatefile)) { - if (preg_match('/^([0-9]{10})\.(php)$/', $updatefile, $matches)) { - $core_version = (int) $matches[1]; - if ($core_version > $version) { - $upgrades[] = $updatefile; - } - } - } - } - - // Sort and execute - asort($upgrades); - - if (sizeof($upgrades) > 0) { - foreach($upgrades as $upgrade) { - // hide all errors. - if ($quiet) { - // hide include errors as well as any exceptions that might happen - try { - if (!@include($CONFIG->path . 'engine/lib/upgrades/' . $upgrade)) { - error_log($e->getmessage()); - } - } catch (Exception $e) { - error_log($e->getmessage()); - } - } else { - include($CONFIG->path . 'engine/lib/upgrades/' . $upgrade); - } - } - } - - return TRUE; - } - - return FALSE; -} - -/** - * Get the current version information - * - * @param true|false $humanreadable Whether to return a human readable version (default: false) - * @return string|false Depending on success - */ -function get_version($humanreadable = false) { - global $CONFIG; - - if (include($CONFIG->path . "version.php")) { - return (!$humanreadable) ? $version : $release; - } - - return FALSE; -} - -/** - * Determines whether or not the database needs to be upgraded. - * - * @return true|false Depending on whether or not the db version matches the code version - */ -function version_upgrade_check() { - $dbversion = (int) datalist_get('version'); - $version = get_version(); - - if ($version > $dbversion) { - return TRUE; - } - - return FALSE; -} - -/** - * Upgrades Elgg - * - */ -function version_upgrade() { - $dbversion = (int) datalist_get('version'); - - // No version number? Oh snap...this is an upgrade from a clean installation < 1.7. - // Run all upgrades without error reporting and hope for the best. - // See http://trac.elgg.org/elgg/ticket/1432 for more. - $quiet = !$dbversion; - - // Upgrade database - if (db_upgrade($dbversion, '', $quiet)) { - system_message(elgg_echo('upgrade:db')); - } - - // Upgrade core - if (upgrade_code($dbversion, $quiet)) { - system_message(elgg_echo('upgrade:core')); - } - - // Now we trigger an event to give the option for plugins to do something - $upgrade_details = new stdClass; - $upgrade_details->from = $dbversion; - $upgrade_details->to = get_version(); - - trigger_elgg_event('upgrade', 'upgrade', $upgrade_details); - - // Update the version - datalist_set('version', get_version()); -} diff --git a/engine/lib/views.php b/engine/lib/views.php new file mode 100644 index 000000000..1142461fe --- /dev/null +++ b/engine/lib/views.php @@ -0,0 +1,1665 @@ +<?php +/** + * Elgg's view system. + * + * The view system is the primary templating engine in Elgg and renders + * all output. Views are short, parameterised PHP scripts for displaying + * output that can be regsitered, overridden, or extended. The view type + * determines the output format and location of the files that renders the view. + * + * Elgg uses a two step process to render full output: first + * content-specific elements are rendered, then the resulting + * content is inserted into a layout and displayed. This makes it + * easy to maintain a consistent look on all pages. + * + * A view corresponds to a single file on the filesystem and the views + * name is its directory structure. A file in + * <code>mod/plugins/views/default/myplugin/example.php</code> + * is called by saying (with the default viewtype): + * <code>echo elgg_view('myplugin/example');</code> + * + * View names that are registered later override those that are + * registered earlier. For plugins this corresponds directly + * to their load order: views in plugins lower in the list override + * those higher in the list. + * + * Plugin views belong in the views/ directory under an appropriate + * viewtype. Views are automatically registered. + * + * Views can be embedded-you can call a view from within a view. + * Views can also be prepended or extended by any other view. + * + * Any view can extend any other view if registered with + * {@link elgg_extend_view()}. + * + * View types are set by passing $_REQUEST['view']. The view type + * 'default' is a standard HTML view. Types can be defined on the fly + * and you can get the current view type with {@link get_current_view()}. + * + * @internal Plugin views are autoregistered before their init functions + * are called, so the init order doesn't affect views. + * + * @internal The file that determines the output of the view is the last + * registered by {@link elgg_set_view_location()}. + * + * @package Elgg.Core + * @subpackage Views + * @link http://docs.elgg.org/Views + */ + +/** + * The view type override. + * + * @global string $CURRENT_SYSTEM_VIEWTYPE + * @see elgg_set_viewtype() + */ +global $CURRENT_SYSTEM_VIEWTYPE; +$CURRENT_SYSTEM_VIEWTYPE = ""; + +/** + * Manually set the viewtype. + * + * View types are detected automatically. This function allows + * you to force subsequent views to use a different viewtype. + * + * @tip Call elgg_set_viewtype() with no parameter to reset. + * + * @param string $viewtype The view type, e.g. 'rss', or 'default'. + * + * @return bool + * @link http://docs.elgg.org/Views/Viewtype + * @example views/viewtype.php + */ +function elgg_set_viewtype($viewtype = "") { + global $CURRENT_SYSTEM_VIEWTYPE; + + $CURRENT_SYSTEM_VIEWTYPE = $viewtype; + + return true; +} + +/** + * Return the current view type. + * + * View types are automatically detected and can be set with $_REQUEST['view'] + * or {@link elgg_set_viewtype()}. + * + * @internal View type is determined in this order: + * - $CURRENT_SYSTEM_VIEWTYPE Any overrides by {@link elgg_set_viewtype()} + * - $CONFIG->view The default view as saved in the DB. + * - $_SESSION['view'] + * + * @return string The view. + * @see elgg_set_viewtype() + * @link http://docs.elgg.org/Views + * @todo This function's sessions stuff needs rewritten, removed, or explained. + */ +function elgg_get_viewtype() { + global $CURRENT_SYSTEM_VIEWTYPE, $CONFIG; + + if ($CURRENT_SYSTEM_VIEWTYPE != "") { + return $CURRENT_SYSTEM_VIEWTYPE; + } + + $viewtype = get_input('view', '', false); + if (is_string($viewtype) && $viewtype !== '') { + // only word characters allowed. + if (!preg_match('/\W/', $viewtype)) { + return $viewtype; + } + } + + if (!empty($CONFIG->view)) { + return $CONFIG->view; + } + + return 'default'; +} + +/** + * Register a view type as valid. + * + * @param string $view_type The view type to register + * @return bool + */ +function elgg_register_viewtype($view_type) { + global $CONFIG; + + if (!isset($CONFIG->view_types) || !is_array($CONFIG->view_types)) { + $CONFIG->view_types = array(); + } + + if (!in_array($view_type, $CONFIG->view_types)) { + $CONFIG->view_types[] = $view_type; + } + + return true; +} + +/** + * Checks if $view_type is valid on this installation. + * + * @param string $view_type View type + * + * @return bool + * @since 1.7.2 + * @access private + */ +function elgg_is_valid_view_type($view_type) { + global $CONFIG; + + if (!isset($CONFIG->view_types) || !is_array($CONFIG->view_types)) { + return FALSE; + } + + return in_array($view_type, $CONFIG->view_types); +} + +/** + * Register a viewtype to fall back to a default view if a view isn't + * found for that viewtype. + * + * @tip This is useful for alternate html viewtypes (such as for mobile devices). + * + * @param string $viewtype The viewtype to register + * + * @return void + * @since 1.7.2 + * @example views/viewtype_fallback.php Fallback from mobile to default. + */ +function elgg_register_viewtype_fallback($viewtype) { + global $CONFIG; + + if (!isset($CONFIG->viewtype)) { + $CONFIG->viewtype = new stdClass; + } + + if (!isset($CONFIG->viewtype->fallback)) { + $CONFIG->viewtype->fallback = array(); + } + + $CONFIG->viewtype->fallback[] = $viewtype; +} + +/** + * Checks if a viewtype falls back to default. + * + * @param string $viewtype Viewtype + * + * @return boolean + * @since 1.7.2 + */ +function elgg_does_viewtype_fallback($viewtype) { + global $CONFIG; + + if (isset($CONFIG->viewtype) && isset($CONFIG->viewtype->fallback)) { + return in_array($viewtype, $CONFIG->viewtype->fallback); + } + + return FALSE; +} + +/** + * Register a view to be available for ajax calls + * + * @param string $view The view name + * @return void + * @since 1.8.3 + */ +function elgg_register_ajax_view($view) { + global $CONFIG; + + if (!isset($CONFIG->allowed_ajax_views)) { + $CONFIG->allowed_ajax_views = array(); + } + + $CONFIG->allowed_ajax_views[$view] = true; +} + +/** + * Unregister a view for ajax calls + * + * @param string $view The view name + * @return void + * @since 1.8.3 + */ +function elgg_unregister_ajax_view($view) { + global $CONFIG; + + if (isset($CONFIG->allowed_ajax_views[$view])) { + unset($CONFIG->allowed_ajax_views[$view]); + } +} + +/** + * Returns the file location for a view. + * + * @warning This doesn't check if the file exists, but only + * constructs (or extracts) the path and returns it. + * + * @param string $view The view. + * @param string $viewtype The viewtype + * + * @return string + */ +function elgg_get_view_location($view, $viewtype = '') { + global $CONFIG; + + if (empty($viewtype)) { + $viewtype = elgg_get_viewtype(); + } + + if (!isset($CONFIG->views->locations[$viewtype][$view])) { + if (!isset($CONFIG->viewpath)) { + return dirname(dirname(dirname(__FILE__))) . "/views/"; + } else { + return $CONFIG->viewpath; + } + } else { + return $CONFIG->views->locations[$viewtype][$view]; + } +} + +/** + * Set an alternative base location for a view. + * + * Views are expected to be in plugin_name/views/. This function can + * be used to change that location. + * + * @internal Core view locations are stored in $CONFIG->viewpath. + * + * @tip This is useful to optionally register views in a plugin. + * + * @param string $view The name of the view + * @param string $location The base location path + * @param string $viewtype The view type + * + * @return void + */ +function elgg_set_view_location($view, $location, $viewtype = '') { + global $CONFIG; + + if (empty($viewtype)) { + $viewtype = 'default'; + } + + if (!isset($CONFIG->views)) { + $CONFIG->views = new stdClass; + } + + if (!isset($CONFIG->views->locations)) { + $CONFIG->views->locations = array($viewtype => array($view => $location)); + + } else if (!isset($CONFIG->views->locations[$viewtype])) { + $CONFIG->views->locations[$viewtype] = array($view => $location); + + } else { + $CONFIG->views->locations[$viewtype][$view] = $location; + } +} + +/** + * Returns whether the specified view exists + * + * @note If $recurse is true, also checks if a view exists only as an extension. + * + * @param string $view The view name + * @param string $viewtype If set, forces the viewtype + * @param bool $recurse If false, do not check extensions + * + * @return bool + */ +function elgg_view_exists($view, $viewtype = '', $recurse = true) { + global $CONFIG; + + // Detect view type + if (empty($viewtype)) { + $viewtype = elgg_get_viewtype(); + } + + if (!isset($CONFIG->views->locations[$viewtype][$view])) { + if (!isset($CONFIG->viewpath)) { + $location = dirname(dirname(dirname(__FILE__))) . "/views/"; + } else { + $location = $CONFIG->viewpath; + } + } else { + $location = $CONFIG->views->locations[$viewtype][$view]; + } + + if (file_exists("{$location}{$viewtype}/{$view}.php")) { + return true; + } + + // If we got here then check whether this exists as an extension + // We optionally recursively check whether the extended view exists also for the viewtype + if ($recurse && isset($CONFIG->views->extensions[$view])) { + foreach ($CONFIG->views->extensions[$view] as $view_extension) { + // do not recursively check to stay away from infinite loops + if (elgg_view_exists($view_extension, $viewtype, false)) { + return true; + } + } + } + + // Now check if the default view exists if the view is registered as a fallback + if ($viewtype != 'default' && elgg_does_viewtype_fallback($viewtype)) { + return elgg_view_exists($view, 'default'); + } + + return false; +} + +/** + * Return a parsed view. + * + * Views are rendered by a template handler and returned as strings. + * + * Views are called with a special $vars variable set, + * which includes any variables passed as the second parameter. + * For backward compatbility, the following variables are also set but we + * recommend that you do not use them: + * - $vars['config'] The $CONFIG global. (Use {@link elgg_get_config()} instead). + * - $vars['url'] The site URL. (use {@link elgg_get_site_url()} instead). + * - $vars['user'] The logged in user. (use {@link elgg_get_logged_in_user_entity()} instead). + * + * Custom template handlers can be set with {@link set_template_handler()}. + * + * The output of views can be intercepted by registering for the + * view, $view_name plugin hook. + * + * @warning Any variables in $_SESSION will override passed vars + * upon name collision. See https://github.com/Elgg/Elgg/issues/2124 + * + * @param string $view The name and location of the view to use + * @param array $vars Variables to pass to the view. + * @param boolean $bypass If set to true, elgg_view will bypass any specified + * alternative template handler; by default, it will + * hand off to this if requested (see set_template_handler) + * @param boolean $ignored This argument is ignored and will be removed eventually + * @param string $viewtype If set, forces the viewtype for the elgg_view call to be + * this value (default: standard detection) + * + * @return string The parsed view + * @see set_template_handler() + * @example views/elgg_view.php + * @link http://docs.elgg.org/View + */ +function elgg_view($view, $vars = array(), $bypass = false, $ignored = false, $viewtype = '') { + global $CONFIG; + + if (!is_string($view) || !is_string($viewtype)) { + elgg_log("View and Viewtype in views must be a strings: $view", 'NOTICE'); + return ''; + } + // basic checking for bad paths + if (strpos($view, '..') !== false) { + return ''; + } + + if (!is_array($vars)) { + elgg_log("Vars in views must be an array: $view", 'ERROR'); + $vars = array(); + } + + // Get the current viewtype + if ($viewtype === '') { + $viewtype = elgg_get_viewtype(); + } elseif (preg_match('/\W/', $viewtype)) { + // Viewtypes can only be alphanumeric + return ''; + } + + $view_orig = $view; + + // Trigger the pagesetup event + if (!isset($CONFIG->pagesetupdone) && $CONFIG->boot_complete) { + $CONFIG->pagesetupdone = true; + elgg_trigger_event('pagesetup', 'system'); + } + + // @warning - plugin authors: do not expect user, config, and url to be + // set by elgg_view() in the future. Instead, use elgg_get_logged_in_user_entity(), + // elgg_get_config(), and elgg_get_site_url() in your views. + if (!isset($vars['user'])) { + $vars['user'] = elgg_get_logged_in_user_entity(); + } + if (!isset($vars['config'])) { + $vars['config'] = $CONFIG; + } + if (!isset($vars['url'])) { + $vars['url'] = elgg_get_site_url(); + } + + // full_view is the new preferred key for full view on entities @see elgg_view_entity() + // check if full_view is set because that means we've already rewritten it and this is + // coming from another view passing $vars directly. + if (isset($vars['full']) && !isset($vars['full_view'])) { + elgg_deprecated_notice("Use \$vars['full_view'] instead of \$vars['full']", 1.8, 2); + $vars['full_view'] = $vars['full']; + } + if (isset($vars['full_view'])) { + $vars['full'] = $vars['full_view']; + } + + // internalname => name (1.8) + if (isset($vars['internalname']) && !isset($vars['__ignoreInternalname']) && !isset($vars['name'])) { + elgg_deprecated_notice('You should pass $vars[\'name\'] now instead of $vars[\'internalname\']', 1.8, 2); + $vars['name'] = $vars['internalname']; + } elseif (isset($vars['name'])) { + if (!isset($vars['internalname'])) { + $vars['__ignoreInternalname'] = ''; + } + $vars['internalname'] = $vars['name']; + } + + // internalid => id (1.8) + if (isset($vars['internalid']) && !isset($vars['__ignoreInternalid']) && !isset($vars['name'])) { + elgg_deprecated_notice('You should pass $vars[\'id\'] now instead of $vars[\'internalid\']', 1.8, 2); + $vars['id'] = $vars['internalid']; + } elseif (isset($vars['id'])) { + if (!isset($vars['internalid'])) { + $vars['__ignoreInternalid'] = ''; + } + $vars['internalid'] = $vars['id']; + } + + // If it's been requested, pass off to a template handler instead + if ($bypass == false && isset($CONFIG->template_handler) && !empty($CONFIG->template_handler)) { + $template_handler = $CONFIG->template_handler; + if (is_callable($template_handler)) { + return call_user_func($template_handler, $view, $vars); + } + } + + // Set up any extensions to the requested view + if (isset($CONFIG->views->extensions[$view])) { + $viewlist = $CONFIG->views->extensions[$view]; + } else { + $viewlist = array(500 => $view); + } + + // Start the output buffer, find the requested view file, and execute it + ob_start(); + + foreach ($viewlist as $priority => $view) { + + $view_location = elgg_get_view_location($view, $viewtype); + $view_file = "$view_location$viewtype/$view.php"; + + // try to include view + if (!file_exists($view_file) || !include($view_file)) { + // requested view does not exist + $error = "$viewtype/$view view does not exist."; + + // attempt to load default view + if ($viewtype !== 'default' && elgg_does_viewtype_fallback($viewtype)) { + + $default_location = elgg_get_view_location($view, 'default'); + $default_view_file = "{$default_location}default/$view.php"; + + if (file_exists($default_view_file) && include($default_view_file)) { + // default view found + $error .= " Using default/$view instead."; + } else { + // no view found at all + $error = "Neither $viewtype/$view nor default/$view view exists."; + } + } + + // log warning + elgg_log($error, 'NOTICE'); + } + } + + // Save the output buffer into the $content variable + $content = ob_get_clean(); + + // Plugin hook + $params = array('view' => $view_orig, 'vars' => $vars, 'viewtype' => $viewtype); + $content = elgg_trigger_plugin_hook('view', $view_orig, $params, $content); + + // backward compatibility with less granular hook will be gone in 2.0 + $content_tmp = elgg_trigger_plugin_hook('display', 'view', $params, $content); + + if ($content_tmp !== $content) { + $content = $content_tmp; + elgg_deprecated_notice('The display:view plugin hook is deprecated by view:view_name', 1.8); + } + + return $content; +} + +/** + * Extends a view with another view. + * + * The output of any view can be prepended or appended to any other view. + * + * The default action is to append a view. If the priority is less than 500, + * the output of the extended view will be appended to the original view. + * + * Priority can be specified and affects the order in which extensions + * are appended or prepended. + * + * @internal View extensions are stored in + * $CONFIG->views->extensions[$view][$priority] = $view_extension + * + * @param string $view The view to extend. + * @param string $view_extension This view is added to $view + * @param int $priority The priority, from 0 to 1000, + * to add at (lowest numbers displayed first) + * + * @return void + * @since 1.7.0 + * @link http://docs.elgg.org/Views/Extend + * @example views/extend.php + */ +function elgg_extend_view($view, $view_extension, $priority = 501) { + global $CONFIG; + + if (!isset($CONFIG->views)) { + $CONFIG->views = (object) array( + 'extensions' => array(), + ); + $CONFIG->views->extensions[$view][500] = (string)$view; + } else { + if (!isset($CONFIG->views->extensions[$view])) { + $CONFIG->views->extensions[$view][500] = (string)$view; + } + } + + // raise priority until it doesn't match one already registered + while (isset($CONFIG->views->extensions[$view][$priority])) { + $priority++; + } + + $CONFIG->views->extensions[$view][$priority] = (string)$view_extension; + ksort($CONFIG->views->extensions[$view]); +} + +/** + * Unextends a view. + * + * @param string $view The view that was extended. + * @param string $view_extension This view that was added to $view + * + * @return bool + * @since 1.7.2 + */ +function elgg_unextend_view($view, $view_extension) { + global $CONFIG; + + if (!isset($CONFIG->views->extensions[$view])) { + return FALSE; + } + + $priority = array_search($view_extension, $CONFIG->views->extensions[$view]); + if ($priority === FALSE) { + return FALSE; + } + + unset($CONFIG->views->extensions[$view][$priority]); + + return TRUE; +} + +/** + * Assembles and outputs a full page. + * + * A "page" in Elgg is determined by the current view type and + * can be HTML for a browser, RSS for a feed reader, or + * Javascript, PHP and a number of other formats. + * + * @param string $title Title + * @param string $body Body + * @param string $page_shell Optional page shell to use. See page/shells view directory + * @param array $vars Optional vars array to pass to the page + * shell. Automatically adds title, body, and sysmessages + * + * @return string The contents of the page + * @since 1.8 + */ +function elgg_view_page($title, $body, $page_shell = 'default', $vars = array()) { + + $messages = null; + if (count_messages()) { + // get messages - try for errors first + $messages = system_messages(NULL, "error"); + if (count($messages["error"]) == 0) { + // no errors so grab rest of messages + $messages = system_messages(null, ""); + } else { + // we have errors - clear out remaining messages + system_messages(null, ""); + } + } + + $vars['title'] = $title; + $vars['body'] = $body; + $vars['sysmessages'] = $messages; + + $vars = elgg_trigger_plugin_hook('output:before', 'page', null, $vars); + + // check for deprecated view + if ($page_shell == 'default' && elgg_view_exists('pageshells/pageshell')) { + elgg_deprecated_notice("pageshells/pageshell is deprecated by page/$page_shell", 1.8); + $output = elgg_view('pageshells/pageshell', $vars); + } else { + $output = elgg_view("page/$page_shell", $vars); + } + + $vars['page_shell'] = $page_shell; + + // Allow plugins to mod output + return elgg_trigger_plugin_hook('output', 'page', $vars, $output); +} + +/** + * Displays a layout with optional parameters. + * + * Layouts provide consistent organization of pages and other blocks of content. + * There are a few default layouts in core: + * - admin A special layout for the admin area. + * - one_column A single content column. + * - one_sidebar A content column with sidebar. + * - two_sidebar A content column with two sidebars. + * - widgets A widget canvas. + * + * The layout views take the form page/layouts/$layout_name + * See the individual layouts for what options are supported. The three most + * common layouts have these parameters: + * one_column + * content => string + * one_sidebar + * content => string + * sidebar => string (optional) + * content + * content => string + * sidebar => string (optional) + * buttons => string (override the default add button) + * title => string (override the default title) + * filter_context => string (selected content filter) + * See the content layout view for more parameters + * + * @param string $layout_name The name of the view in page/layouts/. + * @param array $vars Associative array of parameters for the layout view + * + * @return string The layout + */ +function elgg_view_layout($layout_name, $vars = array()) { + + if (is_string($vars) || $vars === null) { + elgg_deprecated_notice("The use of unlimited optional string arguments in elgg_view_layout() was deprecated in favor of an options array", 1.8); + $arg = 1; + $param_array = array(); + while ($arg < func_num_args()) { + $param_array['area' . $arg] = func_get_arg($arg); + $arg++; + } + } else { + $param_array = $vars; + } + + $params = elgg_trigger_plugin_hook('output:before', 'layout', null, $param_array); + + // check deprecated location + if (elgg_view_exists("canvas/layouts/$layout_name")) { + elgg_deprecated_notice("canvas/layouts/$layout_name is deprecated by page/layouts/$layout_name", 1.8); + $output = elgg_view("canvas/layouts/$layout_name", $params); + } elseif (elgg_view_exists("page/layouts/$layout_name")) { + $output = elgg_view("page/layouts/$layout_name", $params); + } else { + $output = elgg_view("page/layouts/default", $params); + } + + return elgg_trigger_plugin_hook('output:after', 'layout', $params, $output); +} + +/** + * Render a menu + * + * @see elgg_register_menu_item() for documentation on adding menu items and + * navigation.php for information on the different menus available. + * + * This function triggers a 'register', 'menu:<menu name>' plugin hook that enables + * plugins to add menu items just before a menu is rendered. This is used by + * dynamic menus (menus that change based on some input such as the user hover + * menu). Using elgg_register_menu_item() in response to the hook can cause + * incorrect links to show up. See the blog plugin's blog_owner_block_menu() + * for an example of using this plugin hook. + * + * An additional hook is the 'prepare', 'menu:<menu name>' which enables plugins + * to modify the structure of the menu (sort it, remove items, set variables on + * the menu items). + * + * elgg_view_menu() uses views in navigation/menu + * + * @param string $menu_name The name of the menu + * @param array $vars An associative array of display options for the menu. + * Options include: + * sort_by => string or php callback + * string options: 'name', 'priority', 'title' (default), + * 'register' (registration order) or a + * php callback (a compare function for usort) + * handler: string the page handler to build action URLs + * entity: ElggEntity to use to build action URLs + * class: string the class for the entire menu. + * show_section_headers: bool show headers before menu sections. + * + * @return string + * @since 1.8.0 + */ +function elgg_view_menu($menu_name, array $vars = array()) { + global $CONFIG; + + $vars['name'] = $menu_name; + + $sort_by = elgg_extract('sort_by', $vars, 'text'); + + if (isset($CONFIG->menus[$menu_name])) { + $menu = $CONFIG->menus[$menu_name]; + } else { + $menu = array(); + } + + // Give plugins a chance to add menu items just before creation. + // This supports dynamic menus (example: user_hover). + $menu = elgg_trigger_plugin_hook('register', "menu:$menu_name", $vars, $menu); + + $builder = new ElggMenuBuilder($menu); + $vars['menu'] = $builder->getMenu($sort_by); + $vars['selected_item'] = $builder->getSelected(); + + // Let plugins modify the menu + $vars['menu'] = elgg_trigger_plugin_hook('prepare', "menu:$menu_name", $vars, $vars['menu']); + + if (elgg_view_exists("navigation/menu/$menu_name")) { + return elgg_view("navigation/menu/$menu_name", $vars); + } else { + return elgg_view("navigation/menu/default", $vars); + } +} + +/** + * Returns a string of a rendered entity. + * + * Entity views are either determined by setting the view property on the entity + * or by having a view named after the entity $type/$subtype. Entities that have + * neither a view property nor a defined $type/$subtype view will fall back to + * using the $type/default view. + * + * The entity view is called with the following in $vars: + * - ElggEntity 'entity' The entity being viewed + * + * Other common view $vars paramters: + * - bool 'full_view' Whether to show a full or condensed view. + * + * @tip This function can automatically appends annotations to entities if in full + * view and a handler is registered for the entity:annotate. See https://github.com/Elgg/Elgg/issues/964 and + * {@link elgg_view_entity_annotations()}. + * + * @param ElggEntity $entity The entity to display + * @param array $vars Array of variables to pass to the entity view. + * In Elgg 1.7 and earlier it was the boolean $full_view + * @param boolean $bypass If false, will not pass to a custom template handler. + * {@see set_template_handler()} + * @param boolean $debug Complain if views are missing + * + * @return string HTML to display or false + * @link http://docs.elgg.org/Views/Entity + * @link http://docs.elgg.org/Entities + * @todo The annotation hook might be better as a generic plugin hook to append content. + */ +function elgg_view_entity(ElggEntity $entity, $vars = array(), $bypass = true, $debug = false) { + + // No point continuing if entity is null + if (!$entity || !($entity instanceof ElggEntity)) { + return false; + } + + global $autofeed; + $autofeed = true; + + $defaults = array( + 'full_view' => false, + ); + + if (is_array($vars)) { + $vars = array_merge($defaults, $vars); + } else { + elgg_deprecated_notice("Update your use of elgg_view_entity()", 1.8); + $vars = array( + 'full_view' => $vars, + ); + } + + $vars['entity'] = $entity; + + + // if this entity has a view defined, use it + $view = $entity->view; + if (is_string($view)) { + return elgg_view($view, $vars, $bypass, $debug); + } + + $entity_type = $entity->getType(); + + $subtype = $entity->getSubtype(); + if (empty($subtype)) { + $subtype = 'default'; + } + + $contents = ''; + if (elgg_view_exists("$entity_type/$subtype")) { + $contents = elgg_view("$entity_type/$subtype", $vars, $bypass, $debug); + } + if (empty($contents)) { + $contents = elgg_view("$entity_type/default", $vars, $bypass, $debug); + } + + // Marcus Povey 20090616 : Speculative and low impact approach for fixing #964 + if ($vars['full_view']) { + $annotations = elgg_view_entity_annotations($entity, $vars['full_view']); + + if ($annotations) { + $contents .= $annotations; + } + } + return $contents; +} + +/** + * View the icon of an entity + * + * Entity views are determined by having a view named after the entity $type/$subtype. + * Entities that do not have a defined icon/$type/$subtype view will fall back to using + * the icon/$type/default view. + * + * @param ElggEntity $entity The entity to display + * @param string $size The size: tiny, small, medium, large + * @param array $vars An array of variables to pass to the view. Some possible + * variables are img_class and link_class. See the + * specific icon view for more parameters. + * + * @return string HTML to display or false + */ +function elgg_view_entity_icon(ElggEntity $entity, $size = 'medium', $vars = array()) { + + // No point continuing if entity is null + if (!$entity || !($entity instanceof ElggEntity)) { + return false; + } + + $vars['entity'] = $entity; + $vars['size'] = $size; + + $entity_type = $entity->getType(); + + $subtype = $entity->getSubtype(); + if (empty($subtype)) { + $subtype = 'default'; + } + + $contents = ''; + if (elgg_view_exists("icon/$entity_type/$subtype")) { + $contents = elgg_view("icon/$entity_type/$subtype", $vars); + } + if (empty($contents)) { + $contents = elgg_view("icon/$entity_type/default", $vars); + } + if (empty($contents)) { + $contents = elgg_view("icon/default", $vars); + } + + return $contents; +} + +/** + * Returns a string of a rendered annotation. + * + * Annotation views are expected to be in annotation/$annotation_name. + * If a view is not found for $annotation_name, the default annotation/default + * will be used. + * + * @warning annotation/default is not currently defined in core. + * + * The annotation view is called with the following in $vars: + * - ElggEntity 'annotation' The annotation being viewed. + * + * @param ElggAnnotation $annotation The annotation to display + * @param array $vars Variable array for view. + * @param bool $bypass If false, will not pass to a custom + * template handler. {@see set_template_handler()} + * @param bool $debug Complain if views are missing + * + * @return string/false Rendered annotation + */ +function elgg_view_annotation(ElggAnnotation $annotation, array $vars = array(), $bypass = true, $debug = false) { + global $autofeed; + $autofeed = true; + + $defaults = array( + 'full_view' => true, + ); + + $vars = array_merge($defaults, $vars); + $vars['annotation'] = $annotation; + + // @todo setting the view on an annotation is not advertised anywhere + // do we want to keep this? + $view = $annotation->view; + if (is_string($view)) { + return elgg_view($view, $vars, $bypass, $debug); + } + + $name = $annotation->name; + if (empty($name)) { + return false; + } + + if (elgg_view_exists("annotation/$name")) { + return elgg_view("annotation/$name", $vars, $bypass, $debug); + } else { + return elgg_view("annotation/default", $vars, $bypass, $debug); + } +} + +/** + * Returns a rendered list of entities with pagination. This function should be + * called by wrapper functions. + * + * @see elgg_list_entities() + * @see list_user_friends_objects() + * @see elgg_list_entities_from_metadata() + * @see elgg_list_entities_from_relationships() + * @see elgg_list_entities_from_annotations() + * + * @param array $entities Array of entities + * @param array $vars Display variables + * 'count' The total number of entities across all pages + * 'offset' The current indexing offset + * 'limit' The number of entities to display per page + * 'full_view' Display the full view of the entities? + * 'list_class' CSS class applied to the list + * 'item_class' CSS class applied to the list items + * 'pagination' Display pagination? + * 'list_type' List type: 'list' (default), 'gallery' + * 'list_type_toggle' Display the list type toggle? + * + * @return string The rendered list of entities + * @access private + */ +function elgg_view_entity_list($entities, $vars = array(), $offset = 0, $limit = 10, $full_view = true, +$list_type_toggle = true, $pagination = true) { + + if (!$vars["limit"] && !$vars["offset"]) { + // no need for pagination if listing is unlimited
+ $vars["pagination"] = false;
+ }
+ + if (!is_int($offset)) { + $offset = (int)get_input('offset', 0); + } + + // list type can be passed as request parameter + $list_type = get_input('list_type', 'list'); + if (get_input('listtype')) { + elgg_deprecated_notice("'listtype' has been deprecated by 'list_type' for lists", 1.8); + $list_type = get_input('listtype'); + } + + if (is_array($vars)) { + // new function + $defaults = array( + 'items' => $entities, + 'list_class' => 'elgg-list-entity', + 'full_view' => true, + 'pagination' => true, + 'list_type' => $list_type, + 'list_type_toggle' => false, + 'offset' => $offset, + ); + + $vars = array_merge($defaults, $vars); + + } else { + // old function parameters + elgg_deprecated_notice("Please update your use of elgg_view_entity_list()", 1.8); + + $vars = array( + 'items' => $entities, + 'count' => (int) $vars, // the old count parameter + 'offset' => $offset, + 'limit' => (int) $limit, + 'full_view' => $full_view, + 'pagination' => $pagination, + 'list_type' => $list_type, + 'list_type_toggle' => $list_type_toggle, + 'list_class' => 'elgg-list-entity', + ); + } + + if ($vars['list_type'] != 'list') { + return elgg_view('page/components/gallery', $vars); + } else { + return elgg_view('page/components/list', $vars); + } +} + +/** + * Returns a rendered list of annotations, plus pagination. This function + * should be called by wrapper functions. + * + * @param array $annotations Array of annotations + * @param array $vars Display variables + * 'count' The total number of annotations across all pages + * 'offset' The current indexing offset + * 'limit' The number of annotations to display per page + * 'full_view' Display the full view of the annotation? + * 'list_class' CSS Class applied to the list + * 'offset_key' The url parameter key used for offset + * + * @return string The list of annotations + * @access private + */ +function elgg_view_annotation_list($annotations, array $vars = array()) { + $defaults = array( + 'items' => $annotations, + 'list_class' => 'elgg-list-annotation elgg-annotation-list', // @todo remove elgg-annotation-list in Elgg 1.9 + 'full_view' => true, + 'offset_key' => 'annoff', + ); + + $vars = array_merge($defaults, $vars); + + if (!$vars["limit"] && !$vars["offset"]) {
+ // no need for pagination if listing is unlimited
+ $vars["pagination"] = false;
+ } + + return elgg_view('page/components/list', $vars); +} + +/** + * Display a plugin-specified rendered list of annotations for an entity. + * + * This displays the output of functions registered to the entity:annotation, + * $entity_type plugin hook. + * + * This is called automatically by the framework from {@link elgg_view_entity()} + * + * @param ElggEntity $entity Entity + * @param bool $full_view Display full view? + * + * @return mixed string or false on failure + * @todo Change the hook name. + */ +function elgg_view_entity_annotations(ElggEntity $entity, $full_view = true) { + if (!($entity instanceof ElggEntity)) { + return false; + } + + $entity_type = $entity->getType(); + + $annotations = elgg_trigger_plugin_hook('entity:annotate', $entity_type, + array( + 'entity' => $entity, + 'full_view' => $full_view, + ) + ); + + return $annotations; +} + +/** + * Renders a title. + * + * This is a shortcut for {@elgg_view page/elements/title}. + * + * @param string $title The page title + * @param array $vars View variables (was submenu be displayed? (deprecated)) + * + * @return string The HTML (etc) + */ +function elgg_view_title($title, $vars = array()) { + if (!is_array($vars)) { + elgg_deprecated_notice('setting $submenu in elgg_view_title() is deprecated', 1.8); + $vars = array('submenu' => $vars); + } + + $vars['title'] = $title; + + return elgg_view('page/elements/title', $vars); +} + +/** + * Displays a UNIX timestamp in a friendly way + * + * @see elgg_get_friendly_time() + * + * @param int $time A UNIX epoch timestamp + * + * @return string The friendly time HTML + * @since 1.7.2 + */ +function elgg_view_friendly_time($time) { + return elgg_view('output/friendlytime', array('time' => $time)); +} + + +/** + * Returns rendered comments and a comment form for an entity. + * + * @tip Plugins can override the output by registering a handler + * for the comments, $entity_type hook. The handler is responsible + * for formatting the comments and the add comment form. + * + * @param ElggEntity $entity The entity to view comments of + * @param bool $add_comment Include a form to add comments? + * @param array $vars Variables to pass to comment view + * + * @return string|false Rendered comments or false on failure + * @link http://docs.elgg.org/Entities/Comments + * @link http://docs.elgg.org/Annotations/Comments + */ +function elgg_view_comments($entity, $add_comment = true, array $vars = array()) { + if (!($entity instanceof ElggEntity)) { + return false; + } + + $vars['entity'] = $entity; + $vars['show_add_form'] = $add_comment; + $vars['class'] = elgg_extract('class', $vars, "{$entity->getSubtype()}-comments"); + + $output = elgg_trigger_plugin_hook('comments', $entity->getType(), $vars, false); + if ($output) { + return $output; + } else { + return elgg_view('page/elements/comments', $vars); + } +} + +/** + * Wrapper function for the image block display pattern. + * + * Fixed width media on the side (image, icon, flash, etc.). + * Descriptive content filling the rest of the column. + * + * This is a shortcut for {@elgg_view page/components/image_block}. + * + * @param string $image The icon and other information + * @param string $body Description content + * @param array $vars Additional parameters for the view + * + * @return string + * @since 1.8.0 + */ +function elgg_view_image_block($image, $body, $vars = array()) { + $vars['image'] = $image; + $vars['body'] = $body; + return elgg_view('page/components/image_block', $vars); +} + +/** + * Wrapper function for the module display pattern. + * + * Box with header, body, footer + * + * This is a shortcut for {@elgg_view page/components/module}. + * + * @param string $type The type of module (main, info, popup, aside, etc.) + * @param string $title A title to put in the header + * @param string $body Content of the module + * @param array $vars Additional parameters for the module + * + * @return string + * @since 1.8.0 + */ +function elgg_view_module($type, $title, $body, array $vars = array()) { + $vars['class'] = elgg_extract('class', $vars, '') . " elgg-module-$type"; + $vars['title'] = $title; + $vars['body'] = $body; + return elgg_view('page/components/module', $vars); +} + +/** + * Renders a human-readable representation of a river item + * + * @param ElggRiverItem $item A river item object + * @param array $vars An array of variables for the view + * + * @return string returns empty string if could not be rendered + */ +function elgg_view_river_item($item, array $vars = array()) { + if (!($item instanceof ElggRiverItem)) { + return ''; + } + // checking default viewtype since some viewtypes do not have unique views per item (rss) + $view = $item->getView(); + if (!$view || !elgg_view_exists($view, 'default')) { + return ''; + } + + $subject = $item->getSubjectEntity(); + $object = $item->getObjectEntity(); + if (!$subject || !$object) { + // subject is disabled or subject/object deleted + return ''; + } + + // @todo this needs to be cleaned up + // Don't hide objects in closed groups that a user can see. + // see https://github.com/elgg/elgg/issues/4789 + // else { + // // hide based on object's container + // $visibility = ElggGroupItemVisibility::factory($object->container_guid); + // if ($visibility->shouldHideItems) { + // return ''; + // } + // } + + $vars['item'] = $item; + + return elgg_view('river/item', $vars); +} + +/** + * Convenience function for generating a form from a view in a standard location. + * + * This function assumes that the body of the form is located at "forms/$action" and + * sets the action by default to "action/$action". Automatically wraps the forms/$action + * view with a <form> tag and inserts the anti-csrf security tokens. + * + * @tip This automatically appends elgg-form-action-name to the form's class. It replaces any + * slashes with dashes (blog/save becomes elgg-form-blog-save) + * + * @example + * <code>echo elgg_view_form('login');</code> + * + * This would assume a "login" form body to be at "forms/login" and would set the action + * of the form to "http://yoursite.com/action/login". + * + * If elgg_view('forms/login') is: + * <input type="text" name="username" /> + * <input type="password" name="password" /> + * + * Then elgg_view_form('login') generates: + * <form action="http://yoursite.com/action/login" method="post"> + * ...security tokens... + * <input type="text" name="username" /> + * <input type="password" name="password" /> + * </form> + * + * @param string $action The name of the action. An action name does not include + * the leading "action/". For example, "login" is an action name. + * @param array $form_vars $vars environment passed to the "input/form" view + * @param array $body_vars $vars environment passed to the "forms/$action" view + * + * @return string The complete form + */ +function elgg_view_form($action, $form_vars = array(), $body_vars = array()) { + global $CONFIG; + + $defaults = array( + 'action' => $CONFIG->wwwroot . "action/$action", + 'body' => elgg_view("forms/$action", $body_vars) + ); + + $form_class = 'elgg-form-' . preg_replace('/[^a-z0-9]/i', '-', $action); + + // append elgg-form class to any class options set + if (isset($form_vars['class'])) { + $form_vars['class'] = $form_vars['class'] . " $form_class"; + } else { + $form_vars['class'] = $form_class; + } + + return elgg_view('input/form', array_merge($defaults, $form_vars)); +} + +/** + * View an item in a list + * + * @param ElggEntity|ElggAnnotation $item + * @param array $vars Additional parameters for the rendering + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_view_list_item($item, array $vars = array()) { + global $CONFIG; + + $type = $item->getType(); + if (in_array($type, $CONFIG->entity_types)) { + return elgg_view_entity($item, $vars); + } else if ($type == 'annotation') { + return elgg_view_annotation($item, $vars); + } else if ($type == 'river') { + return elgg_view_river_item($item, $vars); + } + + return ''; +} + +/** + * View one of the elgg sprite icons + * + * Shorthand for <span class="elgg-icon elgg-icon-$name"></span> + * + * @param string $name The specific icon to display + * @param string $class Additional class: float, float-alt, or custom class + * + * @return string The html for displaying an icon + */ +function elgg_view_icon($name, $class = '') { + // @todo deprecate boolean in Elgg 1.9 + if ($class === true) { + $class = 'float'; + } + return "<span class=\"elgg-icon elgg-icon-$name $class\"></span>"; +} + +/** + * Displays a user's access collections, using the core/friends/collections view + * + * @param int $owner_guid The GUID of the owning user + * + * @return string A formatted rendition of the collections + * @todo Move to the friends/collection.php page. + * @access private + */ +function elgg_view_access_collections($owner_guid) { + if ($collections = get_user_access_collections($owner_guid)) { + foreach ($collections as $key => $collection) { + $collections[$key]->members = get_members_of_access_collection($collection->id, true); + $collections[$key]->entities = get_user_friends($owner_guid, "", 9999); + } + } + + return elgg_view('core/friends/collections', array('collections' => $collections)); +} + +/** + * Registers a function to handle templates. + * + * Alternative template handlers can be registered to handle + * all output functions. By default, {@link elgg_view()} will + * simply include the view file. If an alternate template handler + * is registered, the view name and passed $vars will be passed to the + * registered function, which is then responsible for generating and returning + * output. + * + * Template handlers need to accept two arguments: string $view_name and array + * $vars. + * + * @warning This is experimental. + * + * @param string $function_name The name of the function to pass to. + * + * @return bool + * @see elgg_view() + * @link http://docs.elgg.org/Views/TemplateHandlers + */ +function set_template_handler($function_name) { + global $CONFIG; + + if (is_callable($function_name)) { + $CONFIG->template_handler = $function_name; + return true; + } + return false; +} + +/** + * Returns the name of views for in a directory. + * + * Use this to get all namespaced views under the first element. + * + * @param string $dir The main directory that holds the views. (mod/profile/views/) + * @param string $base The root name of the view to use, without the viewtype. (profile) + * + * @return array + * @since 1.7.0 + * @todo Why isn't this used anywhere else but in elgg_view_tree()? + * Seems like a useful function for autodiscovery. + * @access private + */ +function elgg_get_views($dir, $base) { + $return = array(); + if (file_exists($dir) && is_dir($dir)) { + if ($handle = opendir($dir)) { + while ($view = readdir($handle)) { + if (!in_array($view, array('.', '..', '.svn', 'CVS'))) { + if (is_dir($dir . '/' . $view)) { + if ($val = elgg_get_views($dir . '/' . $view, $base . '/' . $view)) { + $return = array_merge($return, $val); + } + } else { + $view = str_replace('.php', '', $view); + $return[] = $base . '/' . $view; + } + } + } + } + } + + return $return; +} + +/** + * Returns all views below a partial view. + * + * Settings $view_root = 'profile' will show all available views under + * the "profile" namespace. + * + * @param string $view_root The root view + * @param string $viewtype Optionally specify a view type + * other than the current one. + * + * @return array A list of view names underneath that root view + * @todo This is used once in the deprecated get_activity_stream_data() function. + * @access private + */ +function elgg_view_tree($view_root, $viewtype = "") { + global $CONFIG; + static $treecache = array(); + + // Get viewtype + if (!$viewtype) { + $viewtype = elgg_get_viewtype(); + } + + // A little light internal caching + if (!empty($treecache[$view_root])) { + return $treecache[$view_root]; + } + + // Examine $CONFIG->views->locations + if (isset($CONFIG->views->locations[$viewtype])) { + foreach ($CONFIG->views->locations[$viewtype] as $view => $path) { + $pos = strpos($view, $view_root); + if ($pos === 0) { + $treecache[$view_root][] = $view; + } + } + } + + // Now examine core + $location = $CONFIG->viewpath; + $viewtype = elgg_get_viewtype(); + $root = $location . $viewtype . '/' . $view_root; + + if (file_exists($root) && is_dir($root)) { + $val = elgg_get_views($root, $view_root); + if (!is_array($treecache[$view_root])) { + $treecache[$view_root] = array(); + } + $treecache[$view_root] = array_merge($treecache[$view_root], $val); + } + + return $treecache[$view_root]; +} + +/** + * Auto-registers views from a location. + * + * @note Views in plugin/views/ are automatically registered for active plugins. + * Plugin authors would only need to call this if optionally including + * an entire views structure. + * + * @param string $view_base Optional The base of the view name without the view type. + * @param string $folder Required The folder to begin looking in + * @param string $base_location_path The base views directory to use with elgg_set_view_location() + * @param string $viewtype The type of view we're looking at (default, rss, etc) + * + * @return bool returns false if folder can't be read + * @since 1.7.0 + * @see elgg_set_view_location() + * @todo This seems overly complicated. + * @access private + */ +function autoregister_views($view_base, $folder, $base_location_path, $viewtype) { + if ($handle = opendir($folder)) { + while ($view = readdir($handle)) { + if (!in_array($view, array('.', '..', '.svn', 'CVS')) && !is_dir($folder . "/" . $view)) { + // this includes png files because some icons are stored within view directories. + // See commit [1705] + if ((substr_count($view, ".php") > 0) || (substr_count($view, ".png") > 0)) { + if (!empty($view_base)) { + $view_base_new = $view_base . "/"; + } else { + $view_base_new = ""; + } + + elgg_set_view_location($view_base_new . str_replace('.php', '', $view), + $base_location_path, $viewtype); + } + } else if (!in_array($view, array('.', '..', '.svn', 'CVS')) && is_dir($folder . "/" . $view)) { + if (!empty($view_base)) { + $view_base_new = $view_base . "/"; + } else { + $view_base_new = ""; + } + autoregister_views($view_base_new . $view, $folder . "/" . $view, + $base_location_path, $viewtype); + } + } + return TRUE; + } + + return FALSE; +} + +/** + * Add the rss link to the extras when if needed + * + * @return void + * @access private + */ +function elgg_views_add_rss_link() { + global $autofeed; + if (isset($autofeed) && $autofeed == true) { + $url = current_page_url(); + if (substr_count($url, '?')) { + $url .= "&view=rss"; + } else { + $url .= "?view=rss"; + } + + $url = elgg_format_url($url); + elgg_register_menu_item('extras', array( + 'name' => 'rss', + 'text' => elgg_view_icon('rss'), + 'href' => $url, + 'title' => elgg_echo('feed:rss'), + )); + } +} + +/** + * Registers deprecated views to avoid making some pages from older plugins + * completely empty. + * + * @access private + */ +function elgg_views_handle_deprecated_views() { + $location = elgg_get_view_location('page_elements/contentwrapper'); + if ($location === "/var/www/views/") { + elgg_extend_view('page_elements/contentwrapper', 'page/elements/wrapper'); + } +} + +/** + * Initialize viewtypes on system boot event + * This ensures simplecache is cleared during upgrades. See #2252 + * + * @return void + * @access private + * @elgg_event_handler boot system + */ +function elgg_views_boot() { + global $CONFIG; + + elgg_register_simplecache_view('css/ie'); + elgg_register_simplecache_view('css/ie6'); + elgg_register_simplecache_view('css/ie7'); + + elgg_register_js('jquery', '/vendors/jquery/jquery-1.6.4.min.js', 'head'); + elgg_register_js('jquery-ui', '/vendors/jquery/jquery-ui-1.8.16.min.js', 'head'); + elgg_register_js('jquery.form', '/vendors/jquery/jquery.form.js'); + + elgg_register_simplecache_view('js/elgg'); + $elgg_js_url = elgg_get_simplecache_url('js', 'elgg'); + elgg_register_js('elgg', $elgg_js_url, 'head'); + + elgg_load_js('jquery'); + elgg_load_js('jquery-ui'); + elgg_load_js('elgg'); + + elgg_register_simplecache_view('js/lightbox'); + $lightbox_js_url = elgg_get_simplecache_url('js', 'lightbox'); + elgg_register_js('lightbox', $lightbox_js_url); + + elgg_register_simplecache_view('css/lightbox'); + $lightbox_css_url = elgg_get_simplecache_url('css', 'lightbox'); + elgg_register_css('lightbox', $lightbox_css_url); + + elgg_register_simplecache_view('css/elgg'); + $elgg_css_url = elgg_get_simplecache_url('css', 'elgg'); + elgg_register_css('elgg', $elgg_css_url); + + elgg_load_css('elgg'); + + elgg_register_ajax_view('js/languages'); + + elgg_register_plugin_hook_handler('output:before', 'layout', 'elgg_views_add_rss_link'); + + // discover the built-in view types + // @todo the cache is loaded in load_plugins() but we need to know view_types earlier + $view_path = $CONFIG->viewpath; + + $views = scandir($view_path); + + foreach ($views as $view) { + if ($view[0] !== '.' && is_dir($view_path . $view)) { + elgg_register_viewtype($view); + } + } + + // set default icon sizes - can be overridden in settings.php or with plugin + if (!isset($CONFIG->icon_sizes)) { + $icon_sizes = array( + 'topbar' => array('w' => 16, 'h' => 16, 'square' => TRUE, 'upscale' => TRUE), + 'tiny' => array('w' => 25, 'h' => 25, 'square' => TRUE, 'upscale' => TRUE), + 'small' => array('w' => 40, 'h' => 40, 'square' => TRUE, 'upscale' => TRUE), + 'medium' => array('w' => 100, 'h' => 100, 'square' => TRUE, 'upscale' => TRUE), + 'large' => array('w' => 200, 'h' => 200, 'square' => FALSE, 'upscale' => FALSE), + 'master' => array('w' => 550, 'h' => 550, 'square' => FALSE, 'upscale' => FALSE), + ); + elgg_set_config('icon_sizes', $icon_sizes); + } +} + +elgg_register_event_handler('boot', 'system', 'elgg_views_boot'); +elgg_register_event_handler('init', 'system', 'elgg_views_handle_deprecated_views'); diff --git a/engine/lib/api.php b/engine/lib/web_services.php index 6b773138e..51cad6f39 100644 --- a/engine/lib/api.php +++ b/engine/lib/web_services.php @@ -1,286 +1,13 @@ <?php /** - * Elgg API - * Functions and objects which make up the API engine. + * Elgg web services API + * Functions and objects for exposing custom web services. * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com> - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage WebServicesAPI */ -// Result classes ///////////////////////////////////////////////////////////////////////// - -/** - * GenericResult Result superclass. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Core - */ -abstract class GenericResult { - /** - * The status of the result. - * @var int - */ - private $status_code; - - /** - * Message returned along with the status which is almost always an error message. - * This must be human readable, understandable and localised. - * @var string - */ - private $message; - - /** - * Result store. - * Attach result specific informaton here. - * - * @var mixed. Should probably be an object of some sort. - */ - private $result; - - /** - * Set a status code and optional message. - * - * @param int $status The status code. - * @param string $message The message. - */ - protected function setStatusCode($status, $message = "") { - $this->status_code = $status; - $this->message = $message; - } - - /** - * Set the result. - * - * @param mixed $result - */ - protected function setResult($result) { - $this->result = $result; - } - - protected function getStatusCode() { - return $this->status_code; - } - - protected function getStatusMessage() { - return $this->message; - } - - protected function getResult() { - return $this->result; - } - - /** - * Serialise to a standard class. - * - * DEVNOTE: The API is only interested in data, we can not easily serialise - * custom classes without the need for 1) the other side being PHP, 2) you need to have the class - * definition installed, 3) its the right version! - * - * Therefore, I'm not bothering. - * - * Override this to include any more specific information, however api results should be attached to the - * class using setResult(). - * - * if $CONFIG->debug is set then additional information about the runtime environment and authentication will be - * returned. - * - * @return stdClass Object containing the serialised result. - */ - public function export() { - global $ERRORS, $CONFIG, $_PAM_HANDLERS_MSG; - - $result = new stdClass; - - $result->status = $this->getStatusCode(); - if ($this->getStatusMessage()!="") { - $result->message = $this->getStatusMessage(); - } - - $resultdata = $this->getResult(); - if (isset($resultdata)) { - $result->result = $resultdata; - } - - if (isset($CONFIG->debug)) { - if (count($ERRORS)) { - $result->runtime_errors = $ERRORS; - } - - if (count($_PAM_HANDLERS_MSG)) { - $result->pam = $_PAM_HANDLERS_MSG; - } - } - - return $result; - } -} - -/** - * SuccessResult - * Generic success result class, extend if you want to do something special. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Core - */ -class SuccessResult extends GenericResult { - public static $RESULT_SUCCESS = 0; // Do not change this from 0 - - public function SuccessResult($result) { - $this->setResult($result); - $this->setStatusCode(SuccessResult::$RESULT_SUCCESS); - } - - public static function getInstance($result) { - // Return a new error object. - return new SuccessResult($result); - } -} - -/** - * ErrorResult - * The error result class. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage Core - */ -class ErrorResult extends GenericResult { - // Fail with no specific code - public static $RESULT_FAIL = -1 ; - - public static $RESULT_FAIL_APIKEY_DISABLED = -30; - public static $RESULT_FAIL_APIKEY_INACTIVE = -31; - public static $RESULT_FAIL_APIKEY_INVALID = -32; - - // Invalid, expired or missing auth token - public static $RESULT_FAIL_AUTHTOKEN = -20; - - public function ErrorResult($message, $code = "", Exception $exception = NULL) { - if ($code == "") { - $code = ErrorResult::$RESULT_FAIL; - } - - if ($exception!=NULL) { - $this->setResult($exception->__toString()); - } - - $this->setStatusCode($code, $message); - } - - /** - * Get a new instance of the ErrorResult. - * - * @param string $message - * @param int $code - * @param Exception $exception Optional exception for generating a stack trace. - */ - public static function getInstance($message, $code = "", Exception $exception = NULL) { - // Return a new error object. - return new ErrorResult($message, $code, $exception); - } -} - -// Caching of HMACs /////////////////////////////////////////////////////////////////////// - -/** - * ElggHMACCache - * Store cached data in a temporary database, only used by the HMAC stuff. - * - * @author Curverider Ltd <info@elgg.com> - * @package Elgg - * @subpackage API - */ -class ElggHMACCache extends ElggCache { - /** - * Set the Elgg cache. - * - * @param int $max_age Maximum age in seconds, 0 if no limit. - */ - function __construct($max_age = 0) { - $this->set_variable("max_age", $max_age); - } - - /** - * Save a key - * - * @param string $key - * @param string $data - * @return boolean - */ - public function save($key, $data) { - global $CONFIG; - - $key = sanitise_string($key); - $time = time(); - - return insert_data("INSERT into {$CONFIG->dbprefix}hmac_cache (hmac, ts) VALUES ('$key', '$time')"); - } - - /** - * Load a key - * - * @param string $key - * @param int $offset - * @param int $limit - * @return string - */ - public function load($key, $offset = 0, $limit = null) { - global $CONFIG; - - $key = sanitise_string($key); - - $row = get_data_row("SELECT * from {$CONFIG->dbprefix}hmac_cache where hmac='$key'"); - if ($row) { - return $row->hmac; - } - - return false; - } - - /** - * Invalidate a given key. - * - * @param string $key - * @return bool - */ - public function delete($key) { - global $CONFIG; - - $key = sanitise_string($key); - - return delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where hmac='$key'"); - } - - /** - * Clear out all the contents of the cache. - * - * Not currently implemented in this cache type. - */ - public function clear() { - return true; - } - - /** - * Clean out old stuff. - * - */ - public function __destruct() { - global $CONFIG; - - $time = time(); - $age = (int)$this->get_variable("max_age"); - - $expires = $time-$age; - - delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where ts<$expires"); - } -} - -// Primary Services API Server functions ///////////////////////////////////////////////////////////////////// +// Primary Services API Server functions /** * A global array holding API methods. @@ -290,9 +17,10 @@ class ElggHMACCache extends ElggCache { * "description" => "Some human readable description" * "function" = 'my_function_callback' * "parameters" = array ( - * "variable" = array ( // NB, the order should be the same as the function callback + * "variable" = array ( // the order should be the same as the function callback * type => 'int' | 'bool' | 'float' | 'string' * required => true (default) | false + * default => value // optional * ) * ) * "call_method" = 'GET' | 'POST' @@ -301,6 +29,7 @@ class ElggHMACCache extends ElggCache { * ) * ) */ +global $API_METHODS; $API_METHODS = array(); /** @@ -310,28 +39,38 @@ $API_METHODS = array(); * It also cannot handle arrays of bools or arrays of arrays. * Also, input will be filtered to protect against XSS attacks through the API. * - * @param string $method The api name to expose - for example "myapi.dosomething" - * @param string $function Your function callback. - * @param array $parameters (optional) List of parameters in the same order as in your function. - * Default values may be set for parameters which would allow REST api users flexibility in - * what parameters are passed. Generally, optional parameters should be after required parameters. - * This array should be in the format - * "variable" = array ( - * type => 'int' | 'bool' | 'float' | 'string' | 'array' - * required => true (default) | false - * default => value (optional) - * ) - * @param string $description (optional) human readable description of the function. - * @param string $call_method (optional) Define what http method must be used for this function. Default: GET - * @param bool $require_api_auth (optional) (default is false) Does this method require API authorization? (example: API key) - * @param bool $require_user_auth (optional) (default is false) Does this method require user authorization? + * @param string $method The api name to expose - for example "myapi.dosomething" + * @param string $function Your function callback. + * @param array $parameters (optional) List of parameters in the same order as in + * your function. Default values may be set for parameters which + * allow REST api users flexibility in what parameters are passed. + * Generally, optional parameters should be after required + * parameters. + * + * This array should be in the format + * "variable" = array ( + * type => 'int' | 'bool' | 'float' | 'string' | 'array' + * required => true (default) | false + * default => value (optional) + * ) + * @param string $description (optional) human readable description of the function. + * @param string $call_method (optional) Define what http method must be used for + * this function. Default: GET + * @param bool $require_api_auth (optional) (default is false) Does this method + * require API authorization? (example: API key) + * @param bool $require_user_auth (optional) (default is false) Does this method + * require user authorization? + * * @return bool */ -function expose_function($method, $function, array $parameters = NULL, $description = "", $call_method = "GET", $require_api_auth = false, $require_user_auth = false) { +function expose_function($method, $function, array $parameters = NULL, $description = "", +$call_method = "GET", $require_api_auth = false, $require_user_auth = false) { + global $API_METHODS; if (($method == "") || ($function == "")) { - throw new InvalidParameterException(elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet')); + $msg = elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet'); + throw new InvalidParameterException($msg); } // does not check whether this method has already been exposed - good idea? @@ -344,13 +83,15 @@ function expose_function($method, $function, array $parameters = NULL, $descript if ($parameters != NULL) { if (!is_array($parameters)) { - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:APIParametersArrayStructure'), $method)); + $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method)); + throw new InvalidParameterException($msg); } // catch common mistake of not setting up param array correctly $first = current($parameters); if (!is_array($first)) { - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:APIParametersArrayStructure'), $method)); + $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method)); + throw new InvalidParameterException($msg); } } @@ -375,7 +116,10 @@ function expose_function($method, $function, array $parameters = NULL, $descript $API_METHODS[$method]["call_method"] = 'GET'; break; default : - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:UnrecognisedHttpMethod'), $call_method, $method)); + $msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod', + array($call_method, $method)); + + throw new InvalidParameterException($msg); } $API_METHODS[$method]["require_api_auth"] = $require_api_auth; @@ -387,7 +131,12 @@ function expose_function($method, $function, array $parameters = NULL, $descript /** * Unregister an API method + * * @param string $method The api name that was exposed + * + * @since 1.7.0 + * + * @return void */ function unexpose_function($method) { global $API_METHODS; @@ -399,34 +148,37 @@ function unexpose_function($method) { /** * Check that the method call has the proper API and user authentication + * * @param string $method The api name that was exposed + * * @return true or throws an exception * @throws APIException + * @since 1.7.0 + * @access private */ function authenticate_method($method) { global $API_METHODS; // method must be exposed if (!isset($API_METHODS[$method])) { - throw new APIException(sprintf(elgg_echo('APIException:MethodCallNotImplemented'), $method)); - } - - // make sure that POST variables are available if relevant - if (get_call_method() === 'POST') { - include_post_data(); + throw new APIException(elgg_echo('APIException:MethodCallNotImplemented', array($method))); } // check API authentication if required if ($API_METHODS[$method]["require_api_auth"] == true) { - if (pam_authenticate(null, "api") == false) { + $api_pam = new ElggPAM('api'); + if ($api_pam->authenticate() !== true) { throw new APIException(elgg_echo('APIException:APIAuthenticationFailed')); } } - // check user authentication if required + $user_pam = new ElggPAM('user'); + $user_auth_result = $user_pam->authenticate(array()); + + // check if user authentication is required if ($API_METHODS[$method]["require_user_auth"] == true) { - if (pam_authenticate() == false) { - throw new APIException(elgg_echo('APIException:UserAuthenticationFailed')); + if ($user_auth_result == false) { + throw new APIException($user_pam->getFailureMessage(), ErrorResult::$RESULT_FAIL_AUTHTOKEN); } } @@ -438,25 +190,34 @@ function authenticate_method($method) { * A method is a function which you have previously exposed using expose_function. * * @param string $method Method, e.g. "foo.bar" + * * @return GenericResult The result of the execution. * @throws APIException, CallException + * @access private */ function execute_method($method) { global $API_METHODS, $CONFIG; // method must be exposed if (!isset($API_METHODS[$method])) { - throw new APIException(sprintf(elgg_echo('APIException:MethodCallNotImplemented'), $method)); + $msg = elgg_echo('APIException:MethodCallNotImplemented', array($method)); + throw new APIException($msg); } // function must be callable - if (!(isset($API_METHODS[$method]["function"])) || !(is_callable($API_METHODS[$method]["function"]))) { - throw new APIException(sprintf(elgg_echo('APIException:FunctionDoesNotExist'), $method)); + if (!(isset($API_METHODS[$method]["function"])) + || !(is_callable($API_METHODS[$method]["function"]))) { + + $msg = elgg_echo('APIException:FunctionDoesNotExist', array($method)); + throw new APIException($msg); } // check http call method if (strcmp(get_call_method(), $API_METHODS[$method]["call_method"]) != 0) { - throw new CallException(sprintf(elgg_echo('CallException:InvalidCallMethod'), $method, $API_METHODS[$method]["call_method"])); + $msg = elgg_echo('CallException:InvalidCallMethod', array($method, + $API_METHODS[$method]["call_method"])); + + throw new CallException($msg); } $parameters = get_parameters_for_method($method); @@ -471,6 +232,7 @@ function execute_method($method) { $function = $API_METHODS[$method]["function"]; $serialised_parameters = trim($serialised_parameters, ", "); + // @todo document why we cannot use call_user_func_array here $result = eval("return $function($serialised_parameters);"); // Sanity check result @@ -480,12 +242,14 @@ function execute_method($method) { } if ($result === false) { - throw new APIException(sprintf(elgg_echo('APIException:FunctionParseError'), $function, $serialised_parameters)); + $msg = elgg_echo('APIException:FunctionParseError', array($function, $serialised_parameters)); + throw new APIException($msg); } - if ($result === NULL) { + if ($result === NULL) { // If no value - throw new APIException(sprintf(elgg_echo('APIException:FunctionNoReturn'), $function, $serialised_parameters)); + $msg = elgg_echo('APIException:FunctionNoReturn', array($function, $serialised_parameters)); + throw new APIException($msg); } // Otherwise assume that the call was successful and return it as a success object. @@ -494,7 +258,9 @@ function execute_method($method) { /** * Get the request method. + * * @return string HTTP request method + * @access private */ function get_call_method() { return $_SERVER['REQUEST_METHOD']; @@ -507,7 +273,9 @@ function get_call_method() { * an associated array. * * @param string $method The method + * * @return array containing parameters as key => value + * @access private */ function get_parameters_for_method($method) { global $API_METHODS; @@ -518,7 +286,7 @@ function get_parameters_for_method($method) { if (isset($API_METHODS[$method]['parameters'])) { foreach ($API_METHODS[$method]['parameters'] as $k => $v) { $param = get_input($k); // Make things go through the sanitiser - if ($param !== '') { + if ($param !== '' && $param !== null) { $sanitised[$k] = $param; } else { // parameter wasn't passed so check for default @@ -535,46 +303,27 @@ function get_parameters_for_method($method) { /** * Get POST data * Since this is called through a handler, we need to manually get the post data - * @return POST data from PHP + * + * @return POST data as string encoded as multipart/form-data + * @access private */ function get_post_data() { - global $GLOBALS; - $postdata = ''; - if (isset($GLOBALS['HTTP_RAW_POST_DATA'])) - $postdata = $GLOBALS['HTTP_RAW_POST_DATA']; - - // Attempt another method to return post data (incase always_populate_raw_post_data is switched off) - if (!$postdata) { - $postdata = file_get_contents('php://input'); - } + $postdata = file_get_contents('php://input'); return $postdata; } /** - * This fixes the post parameters that are munged due to page handler - */ -function include_post_data() { - - $postdata = get_post_data(); - - if (isset($postdata)) { - $query_arr = elgg_parse_str($postdata); - if (is_array($query_arr)) { - foreach($query_arr as $name => $val) { - set_input($name, $val); - } - } - } -} - -/** * Verify that the required parameters are present - * @param $method - * @param $parameters + * + * @param string $method Method name + * @param array $parameters List of expected parameters + * * @return true on success or exception * @throws APIException + * @since 1.7.0 + * @access private */ function verify_parameters($method, $parameters) { global $API_METHODS; @@ -588,12 +337,15 @@ function verify_parameters($method, $parameters) { foreach ($API_METHODS[$method]['parameters'] as $key => $value) { // this tests the expose structure: must be array to describe parameter and type must be defined if (!is_array($value) || !isset($value['type'])) { - throw new APIException(sprintf(elgg_echo('APIException:InvalidParameter'), $key, $method)); + + $msg = elgg_echo('APIException:InvalidParameter', array($key, $method)); + throw new APIException($msg); } // Check that the variable is present in the request if required if ($value['required'] && !array_key_exists($key, $parameters)) { - throw new APIException(sprintf(elgg_echo('APIException:MissingParameterInMethod'), $key, $method)); + $msg = elgg_echo('APIException:MissingParameterInMethod', array($key, $method)); + throw new APIException($msg); } } @@ -603,10 +355,13 @@ function verify_parameters($method, $parameters) { /** * Serialize an array of parameters for an API method call * - * @param string $method API method name - * @param array $parameters Array of parameters + * @param string $method API method name + * @param array $parameters Array of parameters + * * @return string or exception * @throws APIException + * @since 1.7.0 + * @access private */ function serialise_parameters($method, $parameters) { global $API_METHODS; @@ -652,7 +407,8 @@ function serialise_parameters($method, $parameters) { case 'array': // we can handle an array of strings, maybe ints, definitely not booleans or other arrays if (!is_array($parameters[$key])) { - throw new APIException(sprintf(elgg_echo('APIException:ParameterNotArray'), $key)); + $msg = elgg_echo('APIException:ParameterNotArray', array($key)); + throw new APIException($msg); } $array = "array("; @@ -664,7 +420,7 @@ function serialise_parameters($method, $parameters) { $array .= "'$k'=>'$v',"; } - $array = trim($array,","); + $array = trim($array, ","); $array .= ")"; $array = ",$array"; @@ -672,7 +428,8 @@ function serialise_parameters($method, $parameters) { $serialised_parameters .= $array; break; default: - throw new APIException(sprintf(elgg_echo('APIException:UnrecognisedTypeCast'), $value['type'], $key, $method)); + $msg = elgg_echo('APIException:UnrecognisedTypeCast', array($value['type'], $key, $method)); + throw new APIException($msg); } } @@ -683,8 +440,13 @@ function serialise_parameters($method, $parameters) { /** * PAM: Confirm that the call includes a valid API key + * * @return true if good API key - otherwise throws exception + * + * @return mixed * @throws APIException + * @since 1.7.0 + * @access private */ function api_auth_key() { global $CONFIG; @@ -704,14 +466,18 @@ function api_auth_key() { // can be used for keeping stats // plugin can also return false to fail this authentication method - return trigger_plugin_hook('api_key', 'use', $api_key, true); + return elgg_trigger_plugin_hook('api_key', 'use', $api_key, true); } /** * PAM: Confirm the HMAC signature + * * @return true if success - otherwise throws exception + * * @throws SecurityException + * @since 1.7.0 + * @access private */ function api_auth_hmac() { global $CONFIG; @@ -723,7 +489,8 @@ function api_auth_hmac() { $api_user = get_api_user($CONFIG->site_id, $api_header->api_key); if (!$api_user) { - throw new SecurityException(elgg_echo('SecurityException:InvalidAPIKey'), ErrorResult::$RESULT_FAIL_APIKEY_INVALID); + throw new SecurityException(elgg_echo('SecurityException:InvalidAPIKey'), + ErrorResult::$RESULT_FAIL_APIKEY_INVALID); } // Get the secret key @@ -752,12 +519,15 @@ function api_auth_hmac() { } // Validate post data - if ($api_header->method=="POST") { + if ($api_header->method == "POST") { $postdata = get_post_data(); $calculated_posthash = calculate_posthash($postdata, $api_header->posthash_algo); - if (strcmp($api_header->posthash, $calculated_posthash)!=0) { - throw new SecurityException(sprintf(elgg_echo('SecurityException:InvalidPostHash'), $calculated_posthash, $api_header->posthash)); + if (strcmp($api_header->posthash, $calculated_posthash) != 0) { + $msg = elgg_echo('SecurityException:InvalidPostHash', + array($calculated_posthash, $api_header->posthash)); + + throw new SecurityException($msg); } } @@ -772,6 +542,7 @@ function api_auth_hmac() { * * @return stdClass Containing all the values. * @throws APIException Detailing any error. + * @access private */ function get_and_validate_api_headers() { $result = new stdClass; @@ -807,7 +578,7 @@ function get_and_validate_api_headers() { // This values determines how long the HMAC cache needs to store previous // signatures. Heavy use of HMAC is better handled with a shorter sig lifetime. // See cache_hmac_check_replay() - if (($result->time<(time()-90000)) || ($result->time>(time()+90000))) { + if (($result->time < (time() - 90000)) || ($result->time > (time() + 90000))) { throw new APIException(elgg_echo('APIException:TemporalDrift')); } @@ -841,13 +612,15 @@ function get_and_validate_api_headers() { * This also gives us an easy way to disable algorithms. * * @param string $algo The algorithm + * * @return string The php algorithm * @throws APIException if an algorithm is not supported. + * @access private */ function map_api_hash($algo) { $algo = strtolower(sanitise_string($algo)); $supported_algos = array( - "md5" => "md5", // TODO: Consider phasing this out + "md5" => "md5", // @todo Consider phasing this out "sha" => "sha1", // alias for sha1 "sha1" => "sha1", "sha256" => "sha256" @@ -857,7 +630,7 @@ function map_api_hash($algo) { return $supported_algos[$algo]; } - throw new APIException(sprintf(elgg_echo('APIException:AlgorithmNotSupported'), $algo)); + throw new APIException(elgg_echo('APIException:AlgorithmNotSupported', array($algo))); } /** @@ -865,15 +638,21 @@ function map_api_hash($algo) { * This function signs an api request using the information provided. The signature returned * has been base64 encoded and then url encoded. * - * @param string $algo The HMAC algorithm used - * @param string $time String representation of unix time - * @param string $api_key Your api key - * @param string $secret Your private key - * @param string $get_variables URLEncoded string representation of the get variable parameters, eg "method=user&guid=2" - * @param string $post_hash Optional sha1 hash of the post data. + * @param string $algo The HMAC algorithm used + * @param string $time String representation of unix time + * @param string $nonce Nonce + * @param string $api_key Your api key + * @param string $secret_key Your private key + * @param string $get_variables URLEncoded string representation of the get variable parameters, + * eg "method=user&guid=2" + * @param string $post_hash Optional sha1 hash of the post data. + * * @return string The HMAC signature + * @access private */ -function calculate_hmac($algo, $time, $nonce, $api_key, $secret_key, $get_variables, $post_hash = "") { +function calculate_hmac($algo, $time, $nonce, $api_key, $secret_key, +$get_variables, $post_hash = "") { + global $CONFIG; elgg_log("HMAC Parts: $algo, $time, $api_key, $secret_key, $get_variables, $post_hash"); @@ -884,7 +663,7 @@ function calculate_hmac($algo, $time, $nonce, $api_key, $secret_key, $get_variab hash_update($ctx, trim($nonce)); hash_update($ctx, trim($api_key)); hash_update($ctx, trim($get_variables)); - if (trim($post_hash)!="") { + if (trim($post_hash) != "") { hash_update($ctx, trim($post_hash)); } @@ -894,11 +673,13 @@ function calculate_hmac($algo, $time, $nonce, $api_key, $secret_key, $get_variab /** * Calculate a hash for some post data. * - * TODO: Work out how to handle really large bits of data. + * @todo Work out how to handle really large bits of data. + * + * @param string $postdata The post data. + * @param string $algo The algorithm used. * - * @param string $postdata string The post data. - * @param string $algo The algorithm used. * @return string The hash. + * @access private */ function calculate_posthash($postdata, $algo) { $ctx = hash_init(map_api_hash($algo)); @@ -913,7 +694,9 @@ function calculate_posthash($postdata, $algo) { * hasn't been seen before, and secondly it will add the given hmac to the cache. * * @param string $hmac The hmac string. + * * @return bool True if replay detected, false if not. + * @access private */ function cache_hmac_check_replay($hmac) { // cache lifetime is 25 hours (this should be related to the time drift @@ -935,6 +718,7 @@ function cache_hmac_check_replay($hmac) { * Generate a new API user for a site, returning a new keypair on success. * * @param int $site_guid The GUID of the site. (default is current site) + * * @return stdClass object or false */ function create_api_user($site_guid) { @@ -946,8 +730,8 @@ function create_api_user($site_guid) { $site_guid = (int)$site_guid; - $public = sha1(rand().$site_guid.microtime()); - $secret = sha1(rand().$site_guid.microtime().$public); + $public = sha1(rand() . $site_guid . microtime()); + $secret = sha1(rand() . $site_guid . microtime() . $public); $insert = insert_data("INSERT into {$CONFIG->dbprefix}api_users (site_guid, api_key, secret) values @@ -961,10 +745,12 @@ function create_api_user($site_guid) { } /** - * Find an API User's details based on the provided public api key. These users are not users in the traditional sense. + * Find an API User's details based on the provided public api key. + * These users are not users in the traditional sense. + * + * @param int $site_guid The GUID of the site. + * @param string $api_key The API Key * - * @param int $site_guid The GUID of the site. - * @param string $api_key The API Key * @return mixed stdClass representing the database row or false. */ function get_api_user($site_guid, $api_key) { @@ -973,14 +759,18 @@ function get_api_user($site_guid, $api_key) { $api_key = sanitise_string($api_key); $site_guid = (int)$site_guid; - return get_data_row("SELECT * from {$CONFIG->dbprefix}api_users where api_key='$api_key' and site_guid=$site_guid and active=1"); + $query = "SELECT * from {$CONFIG->dbprefix}api_users" + . " where api_key='$api_key' and site_guid=$site_guid and active=1"; + + return get_data_row($query); } /** * Revoke an api user key. * - * @param int $site_guid The GUID of the site. - * @param string $api_key The API Key (public). + * @param int $site_guid The GUID of the site. + * @param string $api_key The API Key (public). + * * @return bool */ function remove_api_user($site_guid, $api_key) { @@ -995,7 +785,7 @@ function remove_api_user($site_guid, $api_key) { } -// User Authorization functions //////////////////////////////////////////////////////////////// +// User Authorization functions /** * Check the user token @@ -1003,10 +793,10 @@ function remove_api_user($site_guid, $api_key) { * it is present and is valid. The user gets logged in so with the current * session code of Elgg, that user will be logged out of all other sessions. * - * @param array/mixed $credentials * @return bool + * @access private */ -function pam_auth_usertoken($credentials = NULL) { +function pam_auth_usertoken() { global $CONFIG; $token = get_input('auth_token'); @@ -1025,7 +815,7 @@ function pam_auth_usertoken($credentials = NULL) { } // Not an elgg user - if ( (!$u instanceof ElggUser)) { + if ((!$u instanceof ElggUser)) { return false; } @@ -1047,19 +837,22 @@ function pam_auth_usertoken($credentials = NULL) { /** * See if the user has a valid login sesson + * * @return bool + * @access private */ -function pam_auth_session($credentials = NULL) { - return isloggedin(); +function pam_auth_session() { + return elgg_is_logged_in(); } -// user token functions ///////////////////////////////////////////////////////////////////// +// user token functions /** * Obtain a token for a user. * * @param string $username The username - * @param int $expire minutes until token expires (default is 60 minutes) + * @param int $expire Minutes until token expires (default is 60 minutes) + * * @return bool */ function create_user_token($username, $expire = 60) { @@ -1069,7 +862,7 @@ function create_user_token($username, $expire = 60) { $user = get_user_by_username($username); $time = time(); $time += 60 * $expire; - $token = md5(rand(). microtime() . $username . $time . $site_guid); + $token = md5(rand() . microtime() . $username . $time . $site_guid); if (!$user) { return false; @@ -1077,7 +870,8 @@ function create_user_token($username, $expire = 60) { if (insert_data("INSERT into {$CONFIG->dbprefix}users_apisessions (user_guid, site_guid, token, expires) values - ({$user->guid}, $site_guid, '$token', '$time') on duplicate key update token='$token', expires='$time'")) { + ({$user->guid}, $site_guid, '$token', '$time') + on duplicate key update token='$token', expires='$time'")) { return $token; } @@ -1089,8 +883,10 @@ function create_user_token($username, $expire = 60) { * * @param int $user_guid The user GUID * @param int $site_guid The ID of the site (default is current site) + * * @return false if none available or array of stdClass objects * (see users_apisessions schema for available variables in objects) + * @since 1.7.0 */ function get_user_tokens($user_guid, $site_guid) { global $CONFIG; @@ -1111,11 +907,12 @@ function get_user_tokens($user_guid, $site_guid) { /** * Validate a token against a given site. * - * A token registered with one site can not be used from a different apikey(site), so be aware of this - * during development. + * A token registered with one site can not be used from a + * different apikey(site), so be aware of this during development. + * + * @param string $token The Token. + * @param int $site_guid The ID of the site (default is current site) * - * @param string $token The Token. - * @param int $site_guid The ID of the site (default is current site) * @return mixed The user id attached to the token if not expired or false. */ function validate_user_token($token, $site_guid) { @@ -1143,9 +940,11 @@ function validate_user_token($token, $site_guid) { /** * Remove user token * - * @param string $token - * @param int $site_guid The ID of the site (default is current site) + * @param string $token The toekn + * @param int $site_guid The ID of the site (default is current site) + * * @return bool + * @since 1.7.0 */ function remove_user_token($token, $site_guid) { global $CONFIG; @@ -1165,6 +964,7 @@ function remove_user_token($token, $site_guid) { * Remove expired tokens * * @return bool + * @since 1.7.0 */ function remove_expired_user_tokens() { global $CONFIG; @@ -1177,13 +977,15 @@ function remove_expired_user_tokens() { where site_guid=$site_guid and expires < $time"); } -// Client api functions /////////////////////////////////////////////////////////////////// +// Client api functions /** * Utility function to serialise a header array into its text representation. * * @param array $headers The array of headers "key" => "value" + * * @return string + * @access private */ function serialise_api_headers(array $headers) { $headers_str = ""; @@ -1198,15 +1000,18 @@ function serialise_api_headers(array $headers) { /** * Send a raw API call to an elgg api endpoint. * - * @param array $keys The api keys. - * @param string $url URL of the endpoint. - * @param array $call Associated array of "variable" => "value" - * @param string $method GET or POST - * @param string $post_data The post data + * @param array $keys The api keys. + * @param string $url URL of the endpoint. + * @param array $call Associated array of "variable" => "value" + * @param string $method GET or POST + * @param string $post_data The post data * @param string $content_type The content type + * * @return string */ -function send_api_call(array $keys, $url, array $call, $method = 'GET', $post_data = '', $content_type = 'application/octet-stream') { +function send_api_call(array $keys, $url, array $call, $method = 'GET', $post_data = '', +$content_type = 'application/octet-stream') { + global $CONFIG; $headers = array(); @@ -1218,7 +1023,7 @@ function send_api_call(array $keys, $url, array $call, $method = 'GET', $post_da case 'POST' : break; default: - $msg = sprintf(elgg_echo('NotImplementedException:CallMethodNotImplemented'), $method); + $msg = elgg_echo('NotImplementedException:CallMethodNotImplemented', array($method)); throw new NotImplementedException($msg); } @@ -1229,8 +1034,8 @@ function send_api_call(array $keys, $url, array $call, $method = 'GET', $post_da $nonce = uniqid(''); // URL encode all the parameters - foreach ($call as $k => $v){ - $encoded_params[] = urlencode($k).'='.urlencode($v); + foreach ($call as $k => $v) { + $encoded_params[] = urlencode($k) . '=' . urlencode($v); } $params = implode('&', $encoded_params); @@ -1290,9 +1095,10 @@ function send_api_call(array $keys, $url, array $call, $method = 'GET', $post_da /** * Send a GET call * - * @param string $url URL of the endpoint. - * @param array $call Associated array of "variable" => "value" - * @param array $keys The keys dependant on chosen authentication method + * @param string $url URL of the endpoint. + * @param array $call Associated array of "variable" => "value" + * @param array $keys The keys dependant on chosen authentication method + * * @return string */ function send_api_get_call($url, array $call, array $keys) { @@ -1302,33 +1108,40 @@ function send_api_get_call($url, array $call, array $keys) { /** * Send a GET call * - * @param string $url URL of the endpoint. - * @param array $call Associated array of "variable" => "value" - * @param array $keys The keys dependant on chosen authentication method - * @param string $post_data The post data + * @param string $url URL of the endpoint. + * @param array $call Associated array of "variable" => "value" + * @param array $keys The keys dependant on chosen authentication method + * @param string $post_data The post data * @param string $content_type The content type + * * @return string */ -function send_api_post_call($url, array $call, array $keys, $post_data, $content_type = 'application/octet-stream') { +function send_api_post_call($url, array $call, array $keys, $post_data, +$content_type = 'application/octet-stream') { + return send_api_call($keys, $url, $call, 'POST', $post_data, $content_type); } /** - * Return a key array suitable for the API client using the standard authentication method based on api-keys and secret keys. + * Return a key array suitable for the API client using the standard + * authentication method based on api-keys and secret keys. * * @param string $secret_key Your secret key - * @param string $api_key Your api key + * @param string $api_key Your api key + * * @return array */ function get_standard_api_key_array($secret_key, $api_key) { return array('public' => $api_key, 'private' => $secret_key); } -// System functions /////////////////////////////////////////////////////////////////////// +// System functions /** * Simple api to return a list of all api's installed on the system. + * * @return array + * @access private */ function list_all_apis() { global $API_METHODS; @@ -1347,11 +1160,24 @@ function list_all_apis() { * * @param string $username Username * @param string $password Clear text password + * * @return string Token string or exception * @throws SecurityException + * @access private */ function auth_gettoken($username, $password) { - if (authenticate($username, $password)) { + // check if username is an email address
+ if (is_email_address($username)) {
+ $users = get_user_by_email($username);
+
+ // check if we have a unique user
+ if (is_array($users) && (count($users) == 1)) {
+ $username = $users[0]->username;
+ }
+ }
+
+ // validate username and password + if (true === elgg_authenticate($username, $password)) { $token = create_user_token($username); if ($token) { return $token; @@ -1361,7 +1187,7 @@ function auth_gettoken($username, $password) { throw new SecurityException(elgg_echo('SecurityException:authenticationfailed')); } -// Error handler functions //////////////////////////////////////////////////////////////// +// Error handler functions /** Define a global array of errors */ $ERRORS = array(); @@ -1371,22 +1197,28 @@ $ERRORS = array(); * This function acts as a wrapper to catch and report PHP error messages. * * @see http://uk3.php.net/set-error-handler - * @param int $errno - * @param string $errmsg - * @param string $filename - * @param int $linenum - * @param array $vars - * @return none + * + * @param int $errno Error number + * @param string $errmsg Human readable message + * @param string $filename Filename + * @param int $linenum Line number + * @param array $vars Vars + * + * @return void + * @access private + * + * @throws Exception */ -function __php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { +function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { global $ERRORS; - $error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file " . $filename . " (line " . $linenum . ")"; + $error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file " + . $filename . " (line " . $linenum . ")"; switch ($errno) { case E_USER_ERROR: error_log("ERROR: " . $error); - $ERRORS[] = "ERROR: " .$error; + $ERRORS[] = "ERROR: " . $error; // Since this is a fatal error, we want to stop any further execution but do so gracefully. throw new Exception("ERROR: " . $error); @@ -1395,12 +1227,12 @@ function __php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { case E_WARNING : case E_USER_WARNING : error_log("WARNING: " . $error); - $ERRORS[] = "WARNING: " .$error; + $ERRORS[] = "WARNING: " . $error; break; default: error_log("DEBUG: " . $error); - $ERRORS[] = "DEBUG: " .$error; + $ERRORS[] = "DEBUG: " . $error; } } @@ -1410,56 +1242,50 @@ function __php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { * uncaught exception, end API execution and return the result to the requestor * as an ErrorResult in the requested format. * - * @param Exception $exception - * @return none + * @param Exception $exception Exception + * + * @return void + * @access private */ -function __php_api_exception_handler($exception) { +function _php_api_exception_handler($exception) { error_log("*** FATAL EXCEPTION (API) *** : " . $exception); $code = $exception->getCode() == 0 ? ErrorResult::$RESULT_FAIL : $exception->getCode(); $result = new ErrorResult($exception->getMessage(), $code, NULL); - page_draw($exception->getMessage(), elgg_view("api/output", array("result" => $result))); + echo elgg_view_page($exception->getMessage(), elgg_view("api/output", array("result" => $result))); } -// Services handler /////////////////////////////////////////// +// Services handler /** * Services handler - turns request over to the registered handler * If no handler is found, this returns a 404 error * - * @param string $handler - * @param array $request + * @param string $handler Handler name + * @param array $request Request string + * + * @return void + * @access private */ function service_handler($handler, $request) { global $CONFIG; - // setup the input parameters since this comes through rewrite rule - $query = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], '?')+1); - if (isset($query)) { - $query_arr = elgg_parse_str($query); - if (is_array($query_arr)) { - foreach($query_arr as $name => $val) { - set_input($name, $val); - } - } - } - - set_context('api'); + elgg_set_context('api'); - $request = explode('/',$request); + $request = explode('/', $request); // after the handler, the first identifier is response format - // ex) http://example.org/services/api/rest/xml/?method=test - $reponse_format = array_shift($request); + // ex) http://example.org/services/api/rest/json/?method=test + $response_format = array_shift($request); // Which view - xml, json, ... - if ($reponse_format) { - elgg_set_viewtype($reponse_format); + if ($response_format && elgg_is_valid_view_type($response_format)) { + elgg_set_viewtype($response_format); } else { - // default to xml - elgg_set_viewtype("xml"); + // default to json + elgg_set_viewtype("json"); } if (!isset($CONFIG->servicehandler) || empty($handler)) { @@ -1468,7 +1294,7 @@ function service_handler($handler, $request) { exit; } else if (isset($CONFIG->servicehandler[$handler]) && is_callable($CONFIG->servicehandler[$handler])) { $function = $CONFIG->servicehandler[$handler]; - $function($request, $handler); + call_user_func($function, $request, $handler); } else { // no handler for this web service header("HTTP/1.0 404 Not Found"); @@ -1479,16 +1305,19 @@ function service_handler($handler, $request) { /** * Registers a web services handler * - * @param string $handler web services type + * @param string $handler Web services type * @param string $function Your function name - * @return true|false Depending on success + * + * @return bool Depending on success + * @since 1.7.0 */ function register_service_handler($handler, $function) { global $CONFIG; + if (!isset($CONFIG->servicehandler)) { $CONFIG->servicehandler = array(); } - if (is_callable($function)) { + if (is_callable($function, true)) { $CONFIG->servicehandler[$handler] = $function; return true; } @@ -1502,32 +1331,90 @@ function register_service_handler($handler, $function) { * with register_service_handler(). * * @param string $handler web services type + * + * @return void + * @since 1.7.0 */ function unregister_service_handler($handler) { global $CONFIG; - if (isset($CONFIG->servicehandler) && isset($CONFIG->servicehandler[$handler])) { + + if (isset($CONFIG->servicehandler, $CONFIG->servicehandler[$handler])) { unset($CONFIG->servicehandler[$handler]); } } -// REST handler ////////////////////////////////////////////////////////////// - /** * REST API handler + * + * @return void + * @access private + * + * @throws SecurityException|APIException */ function rest_handler() { global $CONFIG; - require $CONFIG->path . "services/api/rest_api.php"; + // Register the error handler + error_reporting(E_ALL); + set_error_handler('_php_api_error_handler'); + + // Register a default exception handler + set_exception_handler('_php_api_exception_handler'); + + // Check to see if the api is available + if ((isset($CONFIG->disable_api)) && ($CONFIG->disable_api == true)) { + throw new SecurityException(elgg_echo('SecurityException:APIAccessDenied')); + } + + // plugins should return true to control what API and user authentication handlers are registered + if (elgg_trigger_plugin_hook('rest', 'init', null, false) == false) { + // for testing from a web browser, you can use the session PAM + // do not use for production sites!! + //register_pam_handler('pam_auth_session'); + + // user token can also be used for user authentication + register_pam_handler('pam_auth_usertoken'); + + // simple API key check + register_pam_handler('api_auth_key', "sufficient", "api"); + // hmac + register_pam_handler('api_auth_hmac', "sufficient", "api"); + } + + // Get parameter variables + $method = get_input('method'); + $result = null; + + // this will throw an exception if authentication fails + authenticate_method($method); + + $result = execute_method($method); + + + if (!($result instanceof GenericResult)) { + throw new APIException(elgg_echo('APIException:ApiResultUnknown')); + } + + // Output the result + echo elgg_view_page($method, elgg_view("api/output", array("result" => $result))); } -// Initialisation ///////////////////////////////////////////////////////////// +// Initialization /** * Unit tests for API + * + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params + * + * @return array + * @access private */ function api_unit_test($hook, $type, $value, $params) { global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/services/api.php'; return $value; } @@ -1535,27 +1422,33 @@ function api_unit_test($hook, $type, $value, $params) { /** * Initialise the API subsystem. * + * @return void + * @access private */ function api_init() { // Register a page handler, so we can have nice URLs - register_service_handler('rest','rest_handler'); + register_service_handler('rest', 'rest_handler'); - register_plugin_hook('unit_test', 'system', 'api_unit_test'); + elgg_register_plugin_hook_handler('unit_test', 'system', 'api_unit_test'); // expose the list of api methods - expose_function("system.api.list", "list_all_apis", NULL, elgg_echo("system.api.list"), "GET", false, false); + expose_function("system.api.list", "list_all_apis", NULL, + elgg_echo("system.api.list"), "GET", false, false); // The authentication token api - expose_function("auth.gettoken", - "auth_gettoken", array( - 'username' => array ('type' => 'string'), - 'password' => array ('type' => 'string'), - ), - elgg_echo('auth.gettoken'), - 'POST', - false, - false); + expose_function( + "auth.gettoken", + "auth_gettoken", + array( + 'username' => array ('type' => 'string'), + 'password' => array ('type' => 'string'), + ), + elgg_echo('auth.gettoken'), + 'POST', + false, + false + ); } -register_elgg_event_handler('init','system','api_init'); +elgg_register_event_handler('init', 'system', 'api_init'); diff --git a/engine/lib/widgets.php b/engine/lib/widgets.php index a450d6223..699462a1b 100644 --- a/engine/lib/widgets.php +++ b/engine/lib/widgets.php @@ -3,504 +3,418 @@ * Elgg widgets library. * Contains code for handling widgets. * - * @package Elgg - * @subpackage Core - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Widgets */ /** - * Override ElggObject in order to store widget data in ultra-private stores. + * Get widgets for a particular context + * + * The widgets are ordered for display and grouped in columns. + * $widgets = elgg_get_widgets(elgg_get_logged_in_user_guid(), 'dashboard'); + * $first_column_widgets = $widgets[1]; + * + * @param int $user_guid The owner user GUID + * @param string $context The context (profile, dashboard, etc) + * + * @return array An 2D array of ElggWidget objects + * @since 1.8.0 */ -class ElggWidget extends ElggObject { - protected function initialise_attributes() { - parent::initialise_attributes(); - - $this->attributes['subtype'] = "widget"; - } - - public function __construct($guid = null) { - parent::__construct($guid); +function elgg_get_widgets($user_guid, $context) { + $options = array( + 'type' => 'object', + 'subtype' => 'widget', + 'owner_guid' => $user_guid, + 'private_setting_name' => 'context', + 'private_setting_value' => $context, + 'limit' => 0 + ); + $widgets = elgg_get_entities_from_private_settings($options); + if (!$widgets) { + return array(); } - /** - * Override entity get and sets in order to save data to private data store. - */ - 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 private data store. - $meta = get_private_setting($this->guid, $name); - if ($meta) { - return $meta; + $sorted_widgets = array(); + foreach ($widgets as $widget) { + if (!isset($sorted_widgets[(int)$widget->column])) { + $sorted_widgets[(int)$widget->column] = array(); } - - // Can't find it, so return null - return null; + $sorted_widgets[(int)$widget->column][$widget->order] = $widget; } - /** - * Override entity get and sets in order to save data to private data store. - */ - public function set($name, $value) { - if (array_key_exists($name, $this->attributes)) { - // Check that we're not trying to change the guid! - if ((array_key_exists('guid', $this->attributes)) && ($name=='guid')) { - return false; - } - - $this->attributes[$name] = $value; - } else { - return set_private_setting($this->guid, $name, $value); - } - - return true; + foreach ($sorted_widgets as $col => $widgets) { + ksort($sorted_widgets[$col]); } + + return $sorted_widgets; } /** - * Register a particular context for use with widgets. + * Create a new widget instance + * + * @param int $owner_guid GUID of entity that owns this widget + * @param string $handler The handler for this widget + * @param string $context The context for this widget + * @param int $access_id If not specified, it is set to the default access level * - * @param string $context The context we wish to enable context for + * @return int|false Widget GUID or false on failure + * @since 1.8.0 */ -function use_widgets($context) { - global $CONFIG; +function elgg_create_widget($owner_guid, $handler, $context, $access_id = null) { + if (empty($owner_guid) || empty($handler) || !elgg_is_widget_type($handler)) { + return false; + } - if (!isset($CONFIG->widgets)) { - $CONFIG->widgets = new stdClass; + $owner = get_entity($owner_guid); + if (!$owner) { + return false; } - if (!isset($CONFIG->widgets->contexts)) { - $CONFIG->widgets->contexts = array(); + $widget = new ElggWidget; + $widget->owner_guid = $owner_guid; + $widget->container_guid = $owner_guid; // @todo - will this work for group widgets + if (isset($access_id)) { + $widget->access_id = $access_id; + } else { + $widget->access_id = get_default_access(); } - if (!empty($context)) { - $CONFIG->widgets->contexts[] = $context; + if (!$widget->save()) { + return false; } + + // private settings cannot be set until ElggWidget saved + $widget->handler = $handler; + $widget->context = $context; + + return $widget->getGUID(); } /** - * Determines whether or not the current context is using widgets + * Can the user edit the widget layout * - * @return true|false Depending on widget status + * Triggers a 'permissions_check', 'widget_layout' plugin hook + * + * @param string $context The widget context + * @param int $user_guid The GUID of the user (0 for logged in user) + * + * @return bool + * @since 1.8.0 */ -function using_widgets() { - global $CONFIG; +function elgg_can_edit_widget_layout($context, $user_guid = 0) { - $context = get_context(); - if (isset($CONFIG->widgets->contexts) && is_array($CONFIG->widgets->contexts)) { - if (in_array($context, $CONFIG->widgets->contexts)) return true; + $user = get_entity((int)$user_guid); + if (!$user) { + $user = elgg_get_logged_in_user_entity(); } - return false; + $return = false; + if (elgg_is_admin_logged_in()) { + $return = true; + } + if (elgg_get_page_owner_guid() == $user->guid) { + $return = true; + } + + $params = array( + 'user' => $user, + 'context' => $context, + 'page_owner' => elgg_get_page_owner_entity() + ); + return elgg_trigger_plugin_hook('permissions_check', 'widget_layout', $params, $return); } /** - * When given a widget entity and a new requested location, saves the new location - * and also provides a sensible ordering for all widgets in that column + * Regsiter a widget type + * + * This should be called by plugins in their init function. * - * @param ElggObject $widget The widget entity - * @param int $order The order within the column - * @param int $column The column (1, 2 or 3) - * @return true|false Depending on success + * @param string $handler The identifier for the widget handler + * @param string $name The name of the widget type + * @param string $description A description for the widget type + * @param string $context A comma-separated list of contexts where this + * widget is allowed (default: 'all') + * @param bool $multiple Whether or not multiple instances of this widget + * are allowed in a single layout (default: false) + * + * @return bool + * @since 1.8.0 */ -function save_widget_location(ElggObject $widget, $order, $column) { - if ($widget instanceof ElggObject) { - if ($widget->subtype == "widget") { - // If you can't move the widget, don't save a new location - if (!$widget->draggable) { - return false; - } - - // Sanitise the column value - if ($column != 1 || $column != 2 || $column != 3) { - $column = 1; - } +function elgg_register_widget_type($handler, $name, $description, $context = "all", $multiple = false) { - $widget->column = (int) $column; - - $ordertmp = array(); - $params = array( - 'context' => $widget->context, - 'column' => $column, - ); - - if ($entities = get_entities_from_metadata_multi($params,'object','widget')) { - foreach($entities as $entity) { - $entityorder = $entity->order; - if ($entityorder < $order) { - $ordertmp[$entityorder] = $entity; - } - if ($entityorder >= $order) { - $ordertmp[$entityorder + 10000] = $entity; - } - } - } + if (!$handler || !$name) { + return false; + } - $ordertmp[$order] = $widget; - ksort($ordertmp); + global $CONFIG; - $orderticker = 10; - foreach($ordertmp as $orderval => $entity) { - $entity->order = $orderticker; - $orderticker += 10; - } + if (!isset($CONFIG->widgets)) { + $CONFIG->widgets = new stdClass; + } + if (!isset($CONFIG->widgets->handlers)) { + $CONFIG->widgets->handlers = array(); + } - return true; - } else { - register_error($widget->subtype); - } + $handlerobj = new stdClass; + $handlerobj->name = $name; + $handlerobj->description = $description; + $handlerobj->context = explode(",", $context); + $handlerobj->multiple = $multiple; - } + $CONFIG->widgets->handlers[$handler] = $handlerobj; - return false; + return true; } /** - * Get widgets for a particular context and column, in order of display + * Remove a widget type + * + * @param string $handler The identifier for the widget * - * @param int $user_guid The owner user GUID - * @param string $context The context (profile, dashboard etc) - * @param int $column The column (1 or 2) - * @return array|false An array of widget ElggObjects, or false + * @return void + * @since 1.8.0 */ -function get_widgets($user_guid, $context, $column) { - $params = array( - 'column' => $column, - 'context' => $context - ); - $widgets = get_entities_from_private_setting_multi($params, "object", "widget", $user_guid, "", 10000); - if ($widgets) { - $widgetorder = array(); - foreach($widgets as $widget) { - $order = $widget->order; - while(isset($widgetorder[$order])) { - $order++; - } - $widgetorder[$order] = $widget; - } +function elgg_unregister_widget_type($handler) { + global $CONFIG; - ksort($widgetorder); + if (!isset($CONFIG->widgets)) { + return; + } - return $widgetorder; + if (!isset($CONFIG->widgets->handlers)) { + return; } - return false; + if (isset($CONFIG->widgets->handlers[$handler])) { + unset($CONFIG->widgets->handlers[$handler]); + } } /** - * Displays a particular widget + * Has a widget type with the specified handler been registered * - * @param ElggObject $widget The widget to display - * @return string The HTML for the widget, including JavaScript wrapper - */ -function display_widget(ElggObject $widget) { - return elgg_view_entity($widget); -} - -/** - * Add a new widget + * @param string $handler The widget handler identifying string * - * @param int $user_guid User GUID to associate this widget with - * @param string $handler The handler for this widget - * @param string $context The page context for this widget - * @param int $order The order to display this widget in - * @param int $column The column to display this widget in (1, 2 or 3) - * @param int $access_id If not specified, it is set to the default access level - * @return true|false Depending on success + * @return bool Whether or not that widget type exists + * @since 1.8.0 */ -function add_widget($user_guid, $handler, $context, $order = 0, $column = 1, $access_id = null) { - if (empty($user_guid) || empty($context) || empty($handler) || !widget_type_exists($handler)) { - return false; - } - - if ($user = get_user($user_guid)) { - $widget = new ElggWidget; - $widget->owner_guid = $user_guid; - $widget->container_guid = $user_guid; - if (isset($access_id)) { - $widget->access_id = $access_id; - } else { - $widget->access_id = get_default_access(); - } - - if (!$widget->save()) { - return false; - } +function elgg_is_widget_type($handler) { + global $CONFIG; - $widget->handler = $handler; - $widget->context = $context; - $widget->column = $column; - $widget->order = $order; + if (!empty($CONFIG->widgets) && + !empty($CONFIG->widgets->handlers) && + is_array($CONFIG->widgets->handlers) && + array_key_exists($handler, $CONFIG->widgets->handlers)) { - // save_widget_location($widget, $order, $column); return true; - } return false; } /** - * Define a new widget type + * Get the widget types for a context * - * @param string $handler The identifier for the widget handler - * @param string $name The name of the widget type - * @param string $description A description for the widget type - * @param string $context A comma-separated list of contexts where this widget is allowed (default: 'all') - * @param true|false $multiple Whether or not multiple instances of this widget are allowed on a single dashboard (default: false) - * @param string $position A comma-separated list of positions on the page (side or main) where this widget is allowed (default: "side,main") - * @return true|false Depending on success + * The widget types are stdClass objects. + * + * @param string $context The widget context or empty string for current context + * @param bool $exact Only return widgets registered for this context (false) + * + * @return array + * @since 1.8.0 */ +function elgg_get_widget_types($context = "", $exact = false) { + global $CONFIG; -function add_widget_type($handler, $name, $description, $context = "all", $multiple = false, $positions = "side,main") { - if (!empty($handler) && !empty($name)) { - global $CONFIG; + if (empty($CONFIG->widgets) || + empty($CONFIG->widgets->handlers) || + !is_array($CONFIG->widgets->handlers)) { + // no widgets + return array(); + } - if (!isset($CONFIG->widgets)) { - $CONFIG->widgets = new stdClass; - } + if (!$context) { + $context = elgg_get_context(); + } - if (!isset($CONFIG->widgets->handlers)) { - $CONFIG->widgets->handlers = array(); + $widgets = array(); + foreach ($CONFIG->widgets->handlers as $key => $handler) { + if ($exact) { + if (in_array($context, $handler->context)) { + $widgets[$key] = $handler; + } + } else { + if (in_array('all', $handler->context) || in_array($context, $handler->context)) { + $widgets[$key] = $handler; + } } - - $handlerobj = new stdClass; - $handlerobj->name = $name; - $handlerobj->description = $description; - $handlerobj->context = explode(",",$context); - $handlerobj->multiple = $multiple; - $handlerobj->positions = explode(",",$positions); - - $CONFIG->widgets->handlers[$handler] = $handlerobj; - - return true; } - return false; + return $widgets; } /** - * Determines whether or not widgets with the specified handler have been defined + * Regsiter entity of object, widget as ElggWidget objects * - * @param string $handler The widget handler identifying string - * @return true|false Whether or not those widgets exist + * @return void + * @access private */ -function widget_type_exists($handler) { - global $CONFIG; - - if (!empty($CONFIG->widgets) - && !empty($CONFIG->widgets->handlers) - && is_array($CONFIG->widgets->handlers) - && array_key_exists($handler, $CONFIG->widgets->handlers)) { - return true; - } - - return false; +function elgg_widget_run_once() { + add_subtype("object", "widget", "ElggWidget"); } /** - * Returns an array of stdClass objects representing the defined widget types + * Function to initialize widgets functionality * - * @return array A list of types defined (if any) + * @return void + * @access private */ -function get_widget_types() { - global $CONFIG; - - if (!empty($CONFIG->widgets) - && !empty($CONFIG->widgets->handlers) - && is_array($CONFIG->widgets->handlers)) { - - $context = get_context(); - - foreach($CONFIG->widgets->handlers as $key => $handler) { - if (!in_array('all',$handler->context) && - !in_array($context,$handler->context)) { - unset($CONFIG->widgets->handlers[$key]); - } - } - - return $CONFIG->widgets->handlers; - } - - return array(); +function elgg_widgets_init() { + elgg_register_action('widgets/save'); + elgg_register_action('widgets/add'); + elgg_register_action('widgets/move'); + elgg_register_action('widgets/delete'); + elgg_register_action('widgets/upgrade', '', 'admin'); + + run_function_once("elgg_widget_run_once"); } /** - * Saves a widget's settings (by passing an array of (name => value) pairs to save_{$handler}_widget) + * Gets a list of events to create default widgets for and + * register menu items for default widgets with the admin section. + * + * A plugin that wants to register a new context for default widgets should + * register for the plugin hook 'get_list', 'default_widgets'. The handler + * can register the new type of default widgets by adding an associate array to + * the return value array like this: + * array( + * 'name' => elgg_echo('profile'), + * 'widget_context' => 'profile', + * 'widget_columns' => 3, * - * @param int $widget_guid The GUID of the widget we're saving to - * @param array $params An array of name => value parameters + * 'event' => 'create', + * 'entity_type' => 'user', + * 'entity_subtype' => ELGG_ENTITIES_ANY_VALUE, + * ); + * + * The first set of keys define information about the new type of default + * widgets and the second set determine what event triggers the creation of the + * new widgets. + * + * @return void + * @access private */ -function save_widget_info($widget_guid, $params) { - if ($widget = get_entity($widget_guid)) { +function elgg_default_widgets_init() { + global $CONFIG; + $default_widgets = elgg_trigger_plugin_hook('get_list', 'default_widgets', null, array()); - $subtype = $widget->getSubtype(); + $CONFIG->default_widget_info = $default_widgets; - if ($subtype != "widget") { - return false; - } - $handler = $widget->handler; - if (empty($handler) || !widget_type_exists($handler)) { - return false; - } + if ($default_widgets) { + elgg_register_admin_menu_item('configure', 'default_widgets', 'appearance'); - if (!$widget->canEdit()) { - return false; - } + // override permissions for creating widget on logged out / just created entities + elgg_register_plugin_hook_handler('container_permissions_check', 'object', 'elgg_default_widgets_permissions_override'); - // Save the params to the widget - if (is_array($params) && sizeof($params) > 0) { - foreach($params as $name => $value) { - - if (!empty($name) && !in_array($name, array( - 'guid','owner_guid','site_guid' - ))) { - if (is_array($value)) { - // TODO: Handle arrays securely - $widget->setMetaData($name, $value, "", true); - } else { - $widget->$name = $value; - } - } - } - $widget->save(); + // only register the callback once per event + $events = array(); + foreach ($default_widgets as $info) { + $events[$info['event'] . ',' . $info['entity_type']] = $info; } - - $function = "save_{$handler}_widget"; - if (is_callable($function)) { - return $function($params); + foreach ($events as $info) { + elgg_register_event_handler($info['event'], $info['entity_type'], 'elgg_create_default_widgets'); } - - return true; } - - return false; } -function reorder_widgets_from_panel($panelstring1, $panelstring2, $panelstring3, $context, $owner) { - $return = true; - - $mainwidgets = explode('::',$panelstring1); - $sidewidgets = explode('::',$panelstring2); - $rightwidgets = explode('::',$panelstring3); - - $handlers = array(); - $guids = array(); - - if (is_array($mainwidgets) && sizeof($mainwidgets) > 0) { - foreach($mainwidgets as $widget) { - - $guid = (int) $widget; - - if ("{$guid}" == "{$widget}") { - $guids[1][] = $widget; - } else { - $handlers[1][] = $widget; - } - } - } - if (is_array($sidewidgets) && sizeof($sidewidgets) > 0) { - foreach($sidewidgets as $widget) { - - $guid = (int) $widget; - - if ("{$guid}" == "{$widget}") { - $guids[2][] = $widget; - } else { - $handlers[2][] = $widget; - } - - } - } - if (is_array($rightwidgets) && sizeof($rightwidgets) > 0) { - foreach($rightwidgets as $widget) { - - $guid = (int) $widget; - - if ("{$guid}" == "{$widget}") { - $guids[3][] = $widget; - } else { - $handlers[3][] = $widget; - } +/** + * Creates default widgets + * + * This plugin hook handler is registered for events based on what kinds of + * default widgets have been registered. See elgg_default_widgets_init() for + * information on registering new default widget contexts. + * + * @param string $event The event + * @param string $type The type of object + * @param ElggEntity $entity The entity being created + * @return void + * @access private + */ +function elgg_create_default_widgets($event, $type, $entity) { + $default_widget_info = elgg_get_config('default_widget_info'); - } + if (!$default_widget_info || !$entity) { + return; } - // Reorder existing widgets or delete ones that have vanished - foreach (array(1,2,3) as $column) { - if ($dbwidgets = get_widgets($owner,$context,$column)) { - - foreach($dbwidgets as $dbwidget) { - if (in_array($dbwidget->getGUID(),$guids[1]) || in_array($dbwidget->getGUID(),$guids[2]) || in_array($dbwidget->getGUID(),$guids[3])) { - if (in_array($dbwidget->getGUID(),$guids[1])) { - $pos = array_search($dbwidget->getGUID(),$guids[1]); - $col = 1; - } else if (in_array($dbwidget->getGUID(),$guids[2])) { - $pos = array_search($dbwidget->getGUID(),$guids[2]); - $col = 2; - } else { - $pos = array_search($dbwidget->getGUID(),$guids[3]); - $col = 3; + $type = $entity->getType(); + $subtype = $entity->getSubtype(); + + // event is already guaranteed by the hook registration. + // need to check subtype and type. + foreach ($default_widget_info as $info) { + if ($info['entity_type'] == $type) { + if ($info['entity_subtype'] == ELGG_ENTITIES_ANY_VALUE || $info['entity_subtype'] == $subtype) { + + // need to be able to access everything + $old_ia = elgg_set_ignore_access(true); + elgg_push_context('create_default_widgets'); + + // pull in by widget context with widget owners as the site + // not using elgg_get_widgets() because it sorts by columns and we don't care right now. + $options = array( + 'type' => 'object', + 'subtype' => 'widget', + 'owner_guid' => elgg_get_site_entity()->guid, + 'private_setting_name' => 'context', + 'private_setting_value' => $info['widget_context'], + 'limit' => 0 + ); + + $widgets = elgg_get_entities_from_private_settings($options); + /* @var ElggWidget[] $widgets */ + + foreach ($widgets as $widget) { + // change the container and owner + $new_widget = clone $widget; + $new_widget->container_guid = $entity->guid; + $new_widget->owner_guid = $entity->guid; + + // pull in settings + $settings = get_all_private_settings($widget->guid); + + foreach ($settings as $name => $value) { + $new_widget->$name = $value; } - $pos = ($pos + 1) * 10; - $dbwidget->column = $col; - $dbwidget->order = $pos; - } else { - $dbguid = $dbwidget->getGUID(); - if (!$dbwidget->delete()) { - $return = false; - } else { - // Remove state cookie - setcookie('widget' + $dbguid, null); - } - } - } - } - // Add new ones - if (sizeof($guids[$column]) > 0) { - foreach($guids[$column] as $key => $guid) { - if ($guid == 0) { - $pos = ($key + 1) * 10; - $handler = $handlers[$column][$key]; - if (!add_widget($owner,$handler,$context,$pos,$column)) { - $return = false; - } + $new_widget->save(); } + + elgg_set_ignore_access($old_ia); + elgg_pop_context(); } } } - - return $return; -} - -/** - * Run some things once. - * - */ -function widget_run_once() { - // Register a class - add_subtype("object", "widget", "ElggWidget"); } /** - * Function to initialise widgets functionality on Elgg init + * Overrides permissions checks when creating widgets for logged out users. * + * @param string $hook The permissions hook. + * @param string $type The type of entity being created. + * @param string $return Value + * @param mixed $params Params + * @return true|null + * @access private */ -function widgets_init() { - register_action('widgets/reorder'); - register_action('widgets/save'); - register_action('widgets/add'); +function elgg_default_widgets_permissions_override($hook, $type, $return, $params) { + if ($type == 'object' && $params['subtype'] == 'widget') { + return elgg_in_context('create_default_widgets') ? true : null; + } - // Now run this stuff, but only once - run_function_once("widget_run_once"); + return null; } -// Register event -register_elgg_event_handler('init','system','widgets_init'); - -// Use widgets on the dashboard -use_widgets('dashboard');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'elgg_widgets_init'); +// register default widget hooks from plugins +elgg_register_event_handler('ready', 'system', 'elgg_default_widgets_init'); diff --git a/engine/lib/xml-rpc.php b/engine/lib/xml-rpc.php index 7c369edc1..bfe1a8645 100644 --- a/engine/lib/xml-rpc.php +++ b/engine/lib/xml-rpc.php @@ -1,557 +1,203 @@ <?php - /** - * Elgg XML-RPC library. - * Contains functions and classes to handle XML-RPC services, currently only server only. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - - // XMLRPC Call //////////////////////////////////////////////////////////////////////////// - - /** - * @class XMLRPCCall - * This class represents - * @author Curverider Ltd - */ - class XMLRPCCall - { - /** Method name */ - private $methodname; - /** Parameters */ - private $params; - - /** - * Construct a new XML RPC Call - * - * @param string $xml - */ - function __construct($xml) - { - $this->parse($xml); - } - - /** - * Return the method name associated with the call. - * - * @return string - */ - public function getMethodName() { return $this->methodname; } - - /** - * Return the parameters. - * Returns a nested array of XmlElement. - * - * @see XmlElement - * @return array - */ - public function getParameters() { return $this->params; } - - /** - * Parse the xml into its components according to spec. - * This first version is a little primitive. - * - * @param string $xml - */ - private function parse($xml) - { - $xml = xml_to_object($xml); - - // sanity check - if ((isset($xml->name)) && (strcasecmp($xml->name, "methodCall")!=0)) - throw new CallException(elgg_echo('CallException:NotRPCCall')); - - // method name - $this->methodname = $xml->children[0]->content; - - // parameters - $this->params = $xml->children[1]->children; - } - } - - // Response classes /////////////////////////////////////////////////////////////////////// - - /** - * @class XMLRPCParameter Superclass for all RPC parameters. - * @author Curverider Ltd - */ - abstract class XMLRPCParameter - { - protected $value; - - function __construct() { } - - } - - /** - * @class XMLRPCIntParameter An Integer. - * @author Curverider Ltd - */ - class XMLRPCIntParameter extends XMLRPCParameter - { - function __construct($value) - { - parent::__construct(); - - $this->value = (int)$value; - } - - function __toString() - { - return "<value><i4>{$this->value}</i4></value>"; - } - } - - /** - * @class XMLRPCBoolParameter A boolean. - * @author Curverider Ltd - */ - class XMLRPCBoolParameter extends XMLRPCParameter - { - function __construct($value) - { - parent::__construct(); - - $this->value = (bool)$value; - } - - function __toString() - { - $code = ($this->value) ? "1" : "0"; - return "<value><boolean>{$code}</boolean></value>"; - } - } - - /** - * @class XMLRPCStringParameter A string. - * @author Curverider Ltd - */ - class XMLRPCStringParameter extends XMLRPCParameter - { - function __construct($value) - { - parent::__construct(); - - $this->value = $value; - } - - function __toString() - { - $value = htmlentities($this->value); - return "<value><string>{$value}</string></value>"; - } - } - - /** - * @class XMLRPCDoubleParameter A double precision signed floating point number. - * @author Curverider Ltd - */ - class XMLRPCDoubleParameter extends XMLRPCParameter - { - function __construct($value) - { - parent::__construct(); - - $this->value = (float)$value; - } - - function __toString() - { - return "<value><double>{$this->value}</double></value>"; - } - } - - /** - * @class XMLRPCDateParameter An ISO8601 data and time. - * @author Curverider Ltd - */ - class XMLRPCDateParameter extends XMLRPCParameter - { - /** - * Construct a date - * - * @param int $timestamp The unix timestamp, or blank for "now". - */ - function __construct($timestamp = 0) - { - parent::__construct(); - - $this->value = $timestamp; - if (!$timestamp) - $this->value = time(); - } - - function __toString() - { - $value = date('c', $this->value); - return "<value><dateTime.iso8601>{$value}</dateTime.iso8601></value>"; - } - } - - /** - * @class XMLRPCBase64Parameter A base 64 encoded blob of binary. - * @author Curverider Ltd - */ - class XMLRPCBase64Parameter extends XMLRPCParameter - { - /** - * Construct a base64 encoded block - * - * @param string $blob Unencoded binary blob - */ - function __construct($blob) - { - parent::__construct(); - - $this->value = base64_encode($blob); - } - - function __toString() - { - return "<value><base64>{$value}</base64></value>"; - } +/** + * Elgg XML-RPC library. + * Contains functions and classes to handle XML-RPC services, currently only server only. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ + +/** + * parse XMLRPCCall parameters + * + * Convert an XMLRPCCall result array into native data types + * + * @param array $parameters An array of params + * + * @return array + * @access private + */ +function xmlrpc_parse_params($parameters) { + $result = array(); + + foreach ($parameters as $parameter) { + $result[] = xmlrpc_scalar_value($parameter); } - - /** - * @class XMLRPCStructParameter A structure containing other XMLRPCParameter objects. - * @author Curverider Ltd - */ - class XMLRPCStructParameter extends XMLRPCParameter - { - /** - * Construct a struct. - * - * @param array $parameters Optional associated array of parameters, if not provided then addField must be used. - */ - function __construct($parameters = NULL) - { - parent::__construct(); - - if (is_array($parameters)) - { - foreach ($parameters as $k => $v) - $this->addField($k, $v); - } - } - - /** - * Add a field to the container. - * - * @param string $name The name of the field. - * @param XMLRPCParameter $value The value. - */ - public function addField($name, XMLRPCParameter $value) - { - if (!is_array($this->value)) - $this->value = array(); - - $this->value[$name] = $value; - } - - function __toString() - { - $params = ""; - foreach ($this->value as $k => $v) - { - $params .= "<member><name>$k</name>$v</member>"; - } - - return "<value><struct>$params</struct></value>"; - } + + return $result; +} + +/** + * Extract the scalar value of an XMLObject type result array + * + * @param XMLObject $object And object + * + * @return mixed + * @access private + */ +function xmlrpc_scalar_value($object) { + if ($object->name == 'param') { + $object = $object->children[0]->children[0]; } - - /** - * @class XMLRPCArrayParameter An array containing other XMLRPCParameter objects. - * @author Curverider Ltd - */ - class XMLRPCArrayParameter extends XMLRPCParameter - { - /** - * Construct an array. - * - * @param array $parameters Optional array of parameters, if not provided then addField must be used. - */ - function __construct($parameters = NULL) - { - parent::__construct(); - - if (is_array($parameters)) - { - foreach ($parameters as $v) - $this->addField($v); + + switch ($object->name) { + case 'string': + return $object->content; + + case 'array': + foreach ($object->children[0]->children as $child) { + $value[] = xmlrpc_scalar_value($child); } - } - - /** - * Add a field to the container. - * - * @param XMLRPCParameter $value The value. - */ - public function addField(XMLRPCParameter $value) - { - if (!is_array($this->value)) - $this->value = array(); - - $this->value[] = $value; - } - - function __toString() - { - $params = ""; - foreach ($this->value as $value) - { - $params .= "$value"; + return $value; + + case 'struct': + foreach ($object->children as $child) { + if (isset($child->children[1]->children[0])) { + $value[$child->children[0]->content] = xmlrpc_scalar_value($child->children[1]->children[0]); + } else { + $value[$child->children[0]->content] = $child->children[1]->content; + } } - - return "<array><data>$params</data></array>"; - } - } - - /** - * @class XMLRPCResponse XML-RPC Response. - * @author Curverider Ltd - */ - abstract class XMLRPCResponse - { - /** An array of parameters */ - protected $parameters = array(); - - /** - * Add a parameter here. - * - * @param XMLRPCParameter $param The parameter. - */ - public function addParameter(XMLRPCParameter $param) - { - if (!is_array($this->parameters)) - $this->parameters = array(); - - $this->parameters[] = $param; - } + return $value; - public function addInt($value) { $this->addParameter(new XMLRPCIntParameter($value)); } - public function addString($value) { $this->addParameter(new XMLRPCStringParameter($value)); } - public function addDouble($value) { $this->addParameter(new XMLRPCDoubleParameter($value)); } - public function addBoolean($value) { $this->addParameter(new XMLRPCBoolParameter($value)); } - } + case 'boolean': + return (boolean) $object->content; - /** - * @class XMLRPCSuccessResponse - * @author Curverider Ltd - */ - class XMLRPCSuccessResponse extends XMLRPCResponse - { - /** - * Output to XML. - */ - public function __toString() - { - $params = ""; - foreach ($this->parameters as $param) - $params .= "<param>$param</param>\n"; - - return "<methodResponse><params>$params</params></methodResponse>"; - } - } + case 'i4': + case 'int': + return (int) $object->content; - /** - * @class XMLRPCErrorResponse - * @author Curverider Ltd - */ - class XMLRPCErrorResponse extends XMLRPCResponse - { - /** - * Set the error response and error code. - * - * @param string $message The message - * @param int $code Error code (default = system error as defined by http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) - */ - function __construct($message, $code = -32400) - { - $this->addParameter( - new XMLRPCStructParameter( - array ( - 'faultCode' => new XMLRPCIntParameter($code), - 'faultString' => new XMLRPCStringParameter($message) - ) - ) - ); - } - - /** - * Output to XML. - */ - public function __toString() - { - return "<methodResponse><fault><value>{$this->parameters[0]}</value></fault></methodResponse>"; - } - } - - - // Helper functions /////////////////////////////////////////////////////////////////////// - - /** - * parse XMLRPCCall parameters - * - * Convert an XMLRPCCall result array into native data types - * - * @param array $parameters - * @return array - */ - function xmlrpc_parse_params($parameters) - { - $result = array(); - - foreach ($parameters as $parameter) - { - $result[] = xmlrpc_scalar_value($parameter); - } - - return $result; - } + case 'double': + return (double) $object->content; - /** - * Extract the scalar value of an XMLObject type result array - * - * @param XMLObject $object - * @return mixed - */ - function xmlrpc_scalar_value($object) - { - if ($object->name == 'param') - { - $object = $object->children[0]->children[0]; - } - - switch ($object->name) - { - case 'string': - return $object->content; - case 'array': - foreach ($object->children[0]->children as $child) - { - $value[] = xmlrpc_scalar_value($child); - } - return $value; - case 'struct': - foreach ($object->children as $child) - { - if (isset($child->children[1]->children[0])) - $value[$child->children[0]->content] = xmlrpc_scalar_value($child->children[1]->children[0]); - else - $value[$child->children[0]->content] = $child->children[1]->content; - } - return $value; - case 'boolean': - return (boolean) $object->content; - case 'i4': - case 'int': - return (int) $object->content; - case 'double': - return (double) $object->content; - case 'dateTime.iso8601': - return (int) strtotime($object->content); - case 'base64': - return base64_decode($object->content); - case 'value': - return xmlrpc_scalar_value($object->children[0]); - default: - // TODO unsupported, throw an error - return false; - } - } - - // Functions for adding handlers ////////////////////////////////////////////////////////// - - /** XML-RPC Handlers */ - $XML_RPC_HANDLERS = array(); - - /** - * Register a method handler for a given XML-RPC method. - * - * @param string $method Method parameter. - * @param string $handler The handler function. This function accepts once XMLRPCCall object and must return a XMLRPCResponse object. - * @return bool - */ - function register_xmlrpc_handler($method, $handler) - { - global $XML_RPC_HANDLERS; - - $XML_RPC_HANDLERS[$method] = $handler; + case 'dateTime.iso8601': + return (int) strtotime($object->content); + + case 'base64': + return base64_decode($object->content); + + case 'value': + return xmlrpc_scalar_value($object->children[0]); + + default: + // @todo unsupported, throw an error + return false; } - - /** - * Trigger a method call and pass the relevant parameters to the funciton. - * - * @param XMLRPCCall $parameters The call and parameters. - * @return XMLRPCCall - */ - function trigger_xmlrpc_handler(XMLRPCCall $parameters) - { - global $XML_RPC_HANDLERS; - - // Go through and see if we have a handler - if (isset($XML_RPC_HANDLERS[$parameters->getMethodName()])) - { - $handler = $XML_RPC_HANDLERS[$parameters->getMethodName()]; - $result = $handler($parameters); - - if (!($result instanceof XMLRPCResponse)) - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:UnexpectedReturnFormat'), $parameters->getMethodName())); - - // Result in right format, return it. - return $result; +} + +// Functions for adding handlers ////////////////////////////////////////////////////////// + +/** XML-RPC Handlers */ +global $XML_RPC_HANDLERS; +$XML_RPC_HANDLERS = array(); + +/** + * Register a method handler for a given XML-RPC method. + * + * @param string $method Method parameter. + * @param string $handler The handler function. This function accepts + * one XMLRPCCall object and must return a XMLRPCResponse object. + * + * @return bool + */ +function register_xmlrpc_handler($method, $handler) { + global $XML_RPC_HANDLERS; + + $XML_RPC_HANDLERS[$method] = $handler; +} + +/** + * Trigger a method call and pass the relevant parameters to the funciton. + * + * @param XMLRPCCall $parameters The call and parameters. + * + * @return XMLRPCCall + * @access private + */ +function trigger_xmlrpc_handler(XMLRPCCall $parameters) { + global $XML_RPC_HANDLERS; + + // Go through and see if we have a handler + if (isset($XML_RPC_HANDLERS[$parameters->getMethodName()])) { + $handler = $XML_RPC_HANDLERS[$parameters->getMethodName()]; + $result = $handler($parameters); + + if (!($result instanceof XMLRPCResponse)) { + $msg = elgg_echo('InvalidParameterException:UnexpectedReturnFormat', + array($parameters->getMethodName())); + throw new InvalidParameterException($msg); } - - // if no handler then throw exception - throw new NotImplementedException(sprintf(elgg_echo('NotImplementedException:XMLRPCMethodNotImplemented'), $parameters->getMethodName())); + + // Result in right format, return it. + return $result; } - - // Error handler functions //////////////////////////////////////////////////////////////// - - /** - * PHP Error handler function. - * This function acts as a wrapper to catch and report PHP error messages. - * - * @see http://uk3.php.net/set-error-handler - * @param unknown_type $errno - * @param unknown_type $errmsg - * @param unknown_type $filename - * @param unknown_type $linenum - * @param unknown_type $vars - */ - function __php_xmlrpc_error_handler($errno, $errmsg, $filename, $linenum, $vars) - { - $error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file " . $filename . " (line " . $linenum . ")"; - - switch ($errno) { - case E_USER_ERROR: - error_log("ERROR: " . $error); - - // Since this is a fatal error, we want to stop any further execution but do so gracefully. - throw new Exception("ERROR: " . $error); - break; - - case E_WARNING : - case E_USER_WARNING : - error_log("WARNING: " . $error); - break; - - default: - error_log("DEBUG: " . $error); - } + + // if no handler then throw exception + $msg = elgg_echo('NotImplementedException:XMLRPCMethodNotImplemented', + array($parameters->getMethodName())); + throw new NotImplementedException($msg); +} + +/** + * PHP Error handler function. + * This function acts as a wrapper to catch and report PHP error messages. + * + * @see http://uk3.php.net/set-error-handler + * + * @param int $errno Error number + * @param string $errmsg Human readable message + * @param string $filename Filename + * @param int $linenum Line number + * @param array $vars Vars + * + * @return void + * @access private + */ +function _php_xmlrpc_error_handler($errno, $errmsg, $filename, $linenum, $vars) { + $error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file " + . $filename . " (line " . $linenum . ")"; + + switch ($errno) { + case E_USER_ERROR: + error_log("ERROR: " . $error); + + // Since this is a fatal error, we want to stop any further execution but do so gracefully. + throw new Exception("ERROR: " . $error); + break; + + case E_WARNING : + case E_USER_WARNING : + error_log("WARNING: " . $error); + break; + + default: + error_log("DEBUG: " . $error); } - - /** - * PHP Exception handler for XMLRPC. - * @param Exception $exception - */ - function __php_xmlrpc_exception_handler($exception) { - - error_log("*** FATAL EXCEPTION (XML-RPC) *** : " . $exception); - - page_draw($exception->getMessage(), elgg_view("xml-rpc/output", array('result' => new XMLRPCErrorResponse($exception->getMessage(), $exception->getCode()==0 ? -32400 : $exception->getCode())))); +} + +/** + * PHP Exception handler for XMLRPC. + * + * @param Exception $exception The exception + * + * @return void + * @access private + */ +function _php_xmlrpc_exception_handler($exception) { + + error_log("*** FATAL EXCEPTION (XML-RPC) *** : " . $exception); + + $code = $exception->getCode(); + + if ($code == 0) { + $code = -32400; } -?> + + $result = new XMLRPCErrorResponse($exception->getMessage(), $code); + + $vars = array('result' => $result); + + $content = elgg_view("xml-rpc/output", $vars); + + echo elgg_view_page($exception->getMessage(), $content); +} diff --git a/engine/lib/xml.php b/engine/lib/xml.php index f691b2475..497459d83 100644 --- a/engine/lib/xml.php +++ b/engine/lib/xml.php @@ -1,163 +1,111 @@ <?php - /** - * Elgg XML library. - * Contains functions for generating and parsing XML. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - - /** - * @class XmlElement - * A class representing an XML element for import. - */ - class XmlElement - { - /** The name of the element */ - public $name; - - /** The attributes */ - public $attributes; - - /** CData */ - public $content; - - /** Child elements */ - public $children; - }; - - /** - * This function serialises an object recursively into an XML representation. - * The function attempts to call $data->export() which expects a stdClass in return, otherwise it will attempt to - * get the object variables using get_object_vars (which will only return public variables!) - * @param $data object The object to serialise. - * @param $n int Level, only used for recursion. - * @return string The serialised XML output. - */ - function serialise_object_to_xml($data, $name = "", $n = 0) - { - $classname = ($name=="" ? get_class($data) : $name); - - $vars = method_exists($data, "export") ? get_object_vars($data->export()) : get_object_vars($data); - - $output = ""; - - if (($n==0) || ( is_object($data) && !($data instanceof stdClass))) { - $output = "<$classname>"; - } - - foreach ($vars as $key => $value) { - $output .= "<$key type=\"".gettype($value)."\">"; - - if (is_object($value)) { - $output .= serialise_object_to_xml($value, $key, $n+1); - } else if (is_array($value)) { - $output .= serialise_array_to_xml($value, $n+1); - } else if (gettype($value) == "boolean") { - $output .= $value ? "true" : "false"; - } else { - $output .= htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8'); - } - - $output .= "</$key>\n"; - } - - if (($n==0) || ( is_object($data) && !($data instanceof stdClass))) { - $output .= "</$classname>\n"; - } - - return $output; +/** + * Elgg XML library. + * Contains functions for generating and parsing XML. + * + * @package Elgg.Core + * @subpackage XML + */ + +/** + * This function serialises an object recursively into an XML representation. + * + * The function attempts to call $data->export() which expects a stdClass in return, + * otherwise it will attempt to get the object variables using get_object_vars (which + * will only return public variables!) + * + * @param mixed $data The object to serialise. + * @param string $name The name? + * @param int $n Level, only used for recursion. + * + * @return string The serialised XML output. + */ +function serialise_object_to_xml($data, $name = "", $n = 0) { + $classname = ($name == "" ? get_class($data) : $name); + + $vars = method_exists($data, "export") ? get_object_vars($data->export()) : get_object_vars($data); + + $output = ""; + + if (($n == 0) || ( is_object($data) && !($data instanceof stdClass))) { + $output = "<$classname>"; } - /** - * Serialise an array. - * - * @param array $data - * @param int $n Used for recursion - * @return string - */ - function serialise_array_to_xml(array $data, $n = 0) - { - $output = ""; - - if ($n==0) { - $output = "<array>\n"; + foreach ($vars as $key => $value) { + $output .= "<$key type=\"" . gettype($value) . "\">"; + + if (is_object($value)) { + $output .= serialise_object_to_xml($value, $key, $n + 1); + } else if (is_array($value)) { + $output .= serialise_array_to_xml($value, $n + 1); + } else if (gettype($value) == "boolean") { + $output .= $value ? "true" : "false"; + } else { + $output .= htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8'); } - - foreach ($data as $key => $value) { - $item = "array_item"; - - if (is_numeric($key)) { - $output .= "<$item name=\"$key\" type=\"".gettype($value)."\">"; - } else { - $item = $key; - $output .= "<$item type=\"".gettype($value)."\">"; - } - - if (is_object($value)) { - $output .= serialise_object_to_xml($value, "", $n+1); - } else if (is_array($value)) { - $output .= serialise_array_to_xml($value, $n+1); - } else if (gettype($value) == "boolean") { - $output .= $value ? "true" : "false"; - } else { - $output .= htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8'); - } - - $output .= "</$item>\n"; + + $output .= "</$key>\n"; + } + + if (($n == 0) || (is_object($data) && !($data instanceof stdClass))) { + $output .= "</$classname>\n"; + } + + return $output; +} + +/** + * Serialise an array. + * + * @param array $data The data to serialize + * @param int $n Used for recursion + * + * @return string + */ +function serialise_array_to_xml(array $data, $n = 0) { + $output = ""; + + if ($n == 0) { + $output = "<array>\n"; + } + + foreach ($data as $key => $value) { + $item = "array_item"; + + if (is_numeric($key)) { + $output .= "<$item name=\"$key\" type=\"" . gettype($value) . "\">"; + } else { + $item = $key; + $output .= "<$item type=\"" . gettype($value) . "\">"; } - - if ($n==0) { - $output = "</array>\n"; + + if (is_object($value)) { + $output .= serialise_object_to_xml($value, "", $n + 1); + } else if (is_array($value)) { + $output .= serialise_array_to_xml($value, $n + 1); + } else if (gettype($value) == "boolean") { + $output .= $value ? "true" : "false"; + } else { + $output .= htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8'); } - - return $output; + + $output .= "</$item>\n"; } - - /** - * Parse an XML file into an object. - * Based on code from http://de.php.net/manual/en/function.xml-parse-into-struct.php by - * efredricksen at gmail dot com - * - * @param string $xml The XML. - */ - function xml_to_object($xml) - { - $parser = xml_parser_create(); - - // Parse $xml into a structure - xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); - xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); - xml_parse_into_struct($parser, $xml, $tags); - - xml_parser_free($parser); - - $elements = array(); - $stack = array(); - - foreach ($tags as $tag) { - $index = count($elements); - - if ($tag['type'] == "complete" || $tag['type'] == "open") { - $elements[$index] = new XmlElement; - $elements[$index]->name = $tag['tag']; - $elements[$index]->attributes = $tag['attributes']; - $elements[$index]->content = $tag['value']; - - if ($tag['type'] == "open") { - $elements[$index]->children = array(); - $stack[count($stack)] = &$elements; - $elements = &$elements[$index]->children; - } - } - - if ($tag['type'] == "close") { - $elements = &$stack[count($stack) - 1]; - unset($stack[count($stack) - 1]); - } - } - - return $elements[0]; + + if ($n == 0) { + $output .= "</array>\n"; } + + return $output; +} + +/** + * Parse an XML file into an object. + * + * @param string $xml The XML + * + * @return ElggXMLElement + */ +function xml_to_object($xml) { + return new ElggXMLElement($xml); +} |
