diff options
Diffstat (limited to 'engine/lib')
91 files changed, 35520 insertions, 0 deletions
diff --git a/engine/lib/access.php b/engine/lib/access.php new file mode 100644 index 000000000..f7d3bf7ea --- /dev/null +++ b/engine/lib/access.php @@ -0,0 +1,1067 @@ +<?php +/** + * Functions for Elgg's access system for entities, metadata, and annotations. + * + * 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 + */ + +/** + * Return an ElggCache static variable cache for the access caches + * + * @staticvar ElggStaticVariableCache $access_cache + * @return \ElggStaticVariableCache + * @access private + */ +function _elgg_get_access_cache() { + /** + * A default filestore cache using the dataroot. + */ + static $access_cache; + + if (!$access_cache) { + $access_cache = new ElggStaticVariableCache('access'); + } + + return $access_cache; +} + +/** + * Return a string of access_ids for $user_id appropriate for inserting into an SQL IN clause. + * + * @uses get_access_array + * + * @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; + $cache = _elgg_get_access_cache(); + + if ($flush) { + $cache->clear(); + } + + if ($user_id == 0) { + $user_id = elgg_get_logged_in_user_guid(); + } + + if (($site_id == 0) && (isset($CONFIG->site_id))) { + $site_id = $CONFIG->site_id; + } + $user_id = (int) $user_id; + $site_id = (int) $site_id; + + $hash = $user_id . $site_id . 'get_access_list'; + + if ($cache[$hash]) { + return $cache[$hash]; + } + + $access_array = get_access_array($user_id, $site_id, $flush); + $access = "(" . implode(",", $access_array) . ")"; + + if ($init_finished) { + $cache[$hash] = $access; + } + + return $access; +} + +/** + * 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(). + * + * @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; + + $cache = _elgg_get_access_cache(); + + if ($flush) { + $cache->clear(); + } + + if ($user_id == 0) { + $user_id = elgg_get_logged_in_user_guid(); + } + + if (($site_id == 0) && (isset($CONFIG->site_guid))) { + $site_id = $CONFIG->site_guid; + } + + $user_id = (int) $user_id; + $site_id = (int) $site_id; + + $hash = $user_id . $site_id . 'get_access_array'; + + if ($cache[$hash]) { + $access_array = $cache[$hash]; + } else { + $access_array = array(ACCESS_PUBLIC); + + // The following can only return sensible data if the user is logged in. + if (elgg_is_logged_in()) { + $access_array[] = ACCESS_LOGGED_IN; + + // 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)) { + $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)"; + + $collections = get_data($query); + if ($collections) { + foreach ($collections as $collection) { + if (!empty($collection->id)) { + $access_array[] = (int)$collection->id; + } + } + } + + $ignore_access = elgg_check_access_overrides($user_id); + + if ($ignore_access == true) { + $access_array[] = ACCESS_PRIVATE; + } + } + + if ($init_finished) { + $cache[$hash] = $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. + * + * 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; + + if (!$CONFIG->allow_user_default_access) { + return $CONFIG->default_access; + } + + if (!($user) && (!$user = elgg_get_logged_in_user_entity())) { + return $CONFIG->default_access; + } + + if (false !== ($default_access = $user->getPrivateSetting('elgg_default_access'))) { + return $default_access; + } else { + return $CONFIG->default_access; + } +} + +/** + * Allow disabled entities and metadata to be returned by getter functions + * + * @todo Replace this with query object! + * @global bool $ENTITY_SHOW_HIDDEN_OVERRIDE + * @access private + */ +$ENTITY_SHOW_HIDDEN_OVERRIDE = false; + +/** + * Show or hide disabled entities. + * + * @param bool $show_hidden Show disabled entities. + * @return void + * @access private + */ +function access_show_hidden_entities($show_hidden) { + global $ENTITY_SHOW_HIDDEN_OVERRIDE; + $ENTITY_SHOW_HIDDEN_OVERRIDE = $show_hidden; +} + +/** + * Return current status of showing disabled entities. + * + * @return bool + * @access private + */ +function access_get_show_hidden_status() { + global $ENTITY_SHOW_HIDDEN_OVERRIDE; + return $ENTITY_SHOW_HIDDEN_OVERRIDE; +} + +/** + * Returns the SQL where clause for a table with a access_id and enabled columns. + * + * 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 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; + + $sql = ""; + $friends_bit = ""; + $enemies_bit = ""; + + if ($table_prefix) { + $table_prefix = sanitise_string($table_prefix) . "."; + } + + if (!isset($owner)) { + $owner = elgg_get_logged_in_user_guid(); + } + + if (!$owner) { + $owner = -1; + } + + $ignore_access = elgg_check_access_overrides($owner); + $access = get_access_list($owner); + + 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 '; + + // @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_access_restriction_sql('elgg_block_list', "{$table_prefix}owner_guid", $owner, false); + $enemies_bit = '(' + . $enemies_bit + . ' AND ' . get_access_restriction_sql('elgg_filter_list', $owner, "{$table_prefix}owner_guid", false) + . ')'; + } + } + + if (empty($sql)) { + $sql = " $friends_bit ({$table_prefix}access_id IN {$access} + OR ({$table_prefix}owner_guid = {$owner}) + OR ( + {$table_prefix}access_id = " . ACCESS_PRIVATE . " + AND {$table_prefix}owner_guid = $owner + ) + )"; + } + + if ($enemies_bit) { + $sql = "$enemies_bit AND ($sql)"; + } + + if (!$ENTITY_SHOW_HIDDEN_OVERRIDE) { + $sql .= " and {$table_prefix}enabled='yes'"; + } + + return '(' . $sql . ')'; +} + +/** + * 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 user to check access for. Defaults to + * logged in user (which is a useless default). + * + * @return bool + * @link http://docs.elgg.org/Access + */ +function has_access_to_entity($entity, $user = null) { + global $CONFIG; + + if (!isset($user)) { + $access_bit = get_access_sql_suffix("e"); + } else { + $access_bit = get_access_sql_suffix("e", $user->getGUID()); + } + + $query = "SELECT guid from {$CONFIG->dbprefix}entities e WHERE e.guid = " . $entity->getGUID(); + // Add access controls + $query .= " AND " . $access_bit; + if (get_data($query)) { + return true; + } else { + return false; + } +} + +/** + * 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 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, $init_finished; + $cache = _elgg_get_access_cache(); + + if ($flush) { + $cache->clear(); + } + + if ($user_id == 0) { + $user_id = elgg_get_logged_in_user_guid(); + } + + if (($site_id == 0) && (isset($CONFIG->site_id))) { + $site_id = $CONFIG->site_id; + } + + $user_id = (int) $user_id; + $site_id = (int) $site_id; + + $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)"; + + $collections = get_data($query); + if ($collections) { + foreach ($collections as $collection) { + $access_array[$collection->id] = $collection->name; + } + } + + 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 { + $user = elgg_get_logged_in_user_entity(); + } + + $collection = get_access_collection($collection_id); + + 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 collection. + * + * Access colletions allow plugins and users to create granular access + * for entities. + * + * Triggers plugin hook 'access:collections:addcollection', 'collection' + * + * @internal Access collections are stored in the access_collections table. + * Memberships to collections are in access_collections_membership. + * + * @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; + + $name = trim($name); + if (empty($name)) { + return false; + } + + if ($owner_guid == 0) { + $owner_guid = elgg_get_logged_in_user_guid(); + } + if (($site_guid == 0) && (isset($CONFIG->site_guid))) { + $site_guid = $CONFIG->site_guid; + } + $name = sanitise_string($name); + + $q = "INSERT INTO {$CONFIG->dbprefix}access_collections + SET name = '{$name}', + owner_guid = {$owner_guid}, + site_guid = {$site_guid}"; + $id = insert_data($q); + if (!$id) { + return false; + } + + $params = array( + 'collection_id' => $id + ); + + if (!elgg_trigger_plugin_hook('access:collections:addcollection', 'collection', $params, true)) { + return false; + } + + return $id; +} + +/** + * Updates the membership in an access collection. + * + * @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) { + $acl = get_access_collection($collection_id); + + if (!$acl) { + return false; + } + $members = (is_array($members)) ? $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); + + $result = true; + + foreach ($add_members as $guid) { + $result = $result && add_user_to_access_collection($guid, $collection_id); + } + + foreach ($remove_members as $guid) { + $result = $result && remove_user_from_access_collection($guid, $collection_id); + } + + return $result; +} + +/** + * Deletes a specified access collection and its membership. + * + * @param int $collection_id The collection ID + * + * @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; + $params = array('collection_id' => $collection_id); + + if (!elgg_trigger_plugin_hook('access:collections:deletecollection', 'collection', $params, true)) { + 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 object|false + */ +function get_access_collection($collection_id) { + global $CONFIG; + $collection_id = (int) $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 an access collection. + * + * 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 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; + $user = get_user($user_guid); + + $collection = get_access_collection($collection_id); + + if (!($user instanceof Elgguser) || !$collection) { + return false; + } + + $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; + } + + // 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. + * + * Triggers the 'access:collections:remove_user', 'collection' plugin hook. + * + * @param int $user_guid The user GUID + * @param int $collection_id The access collection ID + * + * @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; + $user = get_user($user_guid); + + $collection = get_access_collection($collection_id); + + if (!($user instanceof Elgguser) || !$collection) { + return false; + } + + $params = array( + 'collection_id' => $collection_id, + 'user_guid' => $user_guid + ); + + if (!elgg_trigger_plugin_hook('access:collections:remove_user', 'collection', $params, true)) { + 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); +} + +/** + * 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). + * + * @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, $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; + } + + $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 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) + * + * @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) { + 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}"; + $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}"; + $collection_members = get_data($query); + if (!$collection_members) { + return FALSE; + } + foreach ($collection_members as $key => $val) { + $collection_members[$key] = $val->guid; + } + } + + return $collection_members; +} + +/** + * Return entities based upon access id. + * + * @param array $options Any options accepted by {@link elgg_get_entities()} and + * access_id => int The access ID of the entity. + * + * @see elgg_get_entities() + * @return mixed If count, int. If not count, array. false on errors. + * @since 1.7.0 + */ +function elgg_get_entities_from_access_id(array $options = array()) { + // restrict the resultset to access collection provided + if (!isset($options['access_id'])) { + return FALSE; + } + + // @todo add support for an array of collection_ids + $where = "e.access_id = '{$options['access_id']}'"; + if (isset($options['wheres'])) { + if (is_array($options['wheres'])) { + $options['wheres'][] = $where; + } else { + $options['wheres'] = array($options['wheres'], $where); + } + } else { + $options['wheres'] = array($where); + } + + // return entities with the desired options + return elgg_get_entities($options); +} + +/** + * Lists entities from an access collection + * + * @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 the name of an ACCESS_* constant or a access collection, + * but only if the user has write access on that ACL. + * + * @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(); + + if (array_key_exists($access, $options)) { + return $options[$access]; + } + + // 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->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()->getIgnoreAccess(); +} + +/** + * 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 = 0) { + if (!$user_guid || $user_guid <= 0) { + $is_admin = false; + } else { + $is_admin = elgg_is_admin_user($user_guid); + } + + return ($is_admin || elgg_get_ignore_access()); +} + +/** + * 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; + + if (!$elgg_access) { + $elgg_access = new ElggAccess(); + } + + return $elgg_access; +} + +/** + * 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; + $init_finished = true; +} + +/** + * 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. + * + * @return true|null + * @access private + */ +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)) { + return true; + } + + // check access overrides + if ((elgg_check_access_overrides($user_guid))) { + return true; + } + + // consult other hooks + return NULL; +} + +/** + * Runs unit tests for the entities object. + * @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 +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 new file mode 100644 index 000000000..53b185dea --- /dev/null +++ b/engine/lib/actions.php @@ -0,0 +1,490 @@ +<?php +/** + * Elgg Actions + * + * 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 + */ + +/** + * 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; + + $action = rtrim($action, '/'); + + // @todo REMOVE THESE ONCE #1509 IS IN PLACE. + // Allow users to disable plugins without a token in order to + // remove plugins that are incompatible. + // Login and logout are for convenience. + // file/download (see #2010) + $exceptions = array( + 'admin/plugins/disable', + 'logout', + 'login', + 'file/download', + ); + + if (!in_array($action, $exceptions)) { + // All actions require a token. + action_gatekeeper(); + } + + $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 (!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))); + } + } + } + + $forwarder = empty($forwarder) ? REFERER : $forwarder; + forward($forwarder); +} + +/** + * 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/. + * + * $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 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(); + } + + if (empty($filename)) { + $path = ""; + if (isset($CONFIG->path)) { + $path = $CONFIG->path; + } + + $filename = $path . "actions/" . $action . ".php"; + } + + $CONFIG->actions[$action] = array( + 'file' => $filename, + 'access' => $access, + ); + return true; +} + +/** + * Unregisters an action + * + * @param string $action Action name + * @return bool + * @since 1.8.1 + */ +function elgg_unregister_action($action) { + global $CONFIG; + + if (isset($CONFIG->actions[$action])) { + unset($CONFIG->actions[$action]); + return true; + } else { + return false; + } +} + +/** + * 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 bool + * @see generate_action_token() + * @link http://docs.elgg.org/Actions/Tokens + * @access private + */ +function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) { + global $CONFIG; + + if (!$token) { + $token = get_input('__elgg_token'); + } + + if (!$ts) { + $ts = get_input('__elgg_ts'); + } + + if (!isset($CONFIG->action_token_timeout)) { + // default to 2 hours + $timeout = 2; + } else { + $timeout = $CONFIG->action_token_timeout; + } + + $session_id = session_id(); + + if (($token) && ($ts) && ($session_id)) { + // generate token, check with input and forward if invalid + $generated_token = generate_action_token($ts); + + // Validate token + if ($token == $generated_token) { + $hour = 60 * 60; + $timeout = $timeout * $hour; + $now = time(); + + // Validate time to ensure its not crazy + if ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout)) { + // We have already got this far, so unless anything + // else says something to the contry we assume we're ok + $returnval = true; + + $returnval = elgg_trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', array( + 'token' => $token, + 'time' => $ts + ), $returnval); + + if ($returnval) { + return true; + } else if ($visibleerrors) { + register_error(elgg_echo('actiongatekeeper:pluginprevents')); + } + } else if ($visibleerrors) { + register_error(elgg_echo('actiongatekeeper:timeerror')); + } + } else if ($visibleerrors) { + 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); + } + } + + return FALSE; +} + +/** + * 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. + * + * @return mixed True if valid or redirects. + * @access private + */ +function action_gatekeeper() { + if (validate_action_token()) { + return TRUE; + } + + forward(REFERER, 'csrf'); +} + +/** + * 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) { + $site_secret = get_site_secret(); + $session_id = session_id(); + // Session token + $st = $_SESSION['__elgg_session']; + + if (($site_secret) && ($session_id)) { + return md5($site_secret . $timestamp . $session_id . $st); + } + + return FALSE; +} + +/** + * Initialise the site secret hash. + * + * Used during installation and saves as a datalist. + * + * @return mixed The site secret hash or false + * @access private + * @todo Move to better file. + */ +function init_site_secret() { + $secret = md5(rand() . microtime()); + if (datalist_set('__site_secret__', $secret)) { + return $secret; + } + + return FALSE; +} + +/** + * 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__'); + if (!$secret) { + $secret = init_site_secret(); + } + + return $secret; +} + +/** + * 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/admin.php b/engine/lib/admin.php new file mode 100644 index 000000000..35ab5599d --- /dev/null +++ b/engine/lib/admin.php @@ -0,0 +1,648 @@ +<?php +/** + * Elgg admin functions. + * + * 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); +} + +/** + * 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 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; +} + +/** + * 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; +} + +/** + * List all admin messages. + * + * @param int $limit Limit + * + * @return array List 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) { + $notice = elgg_get_entities_from_metadata(array( + 'type' => 'object', + 'subtype' => 'admin_notice', + 'metadata_name_value_pair' => array('name' => 'admin_notice_id', 'value' => $id) + )); + + 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() { + 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/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', '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'); +} + +/** + * Create the plugin settings page menu. + * + * 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 FALSE; + } + + 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']; + foreach ($configure_items as $menu_item) { + if ($menu_item->getName() == 'settings') { + $settings = $menu_item; + } + } + + // keep the basic and advanced settings at the top + $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 (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', + )); + + 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. Expects corresponding views as admin/section/subsection + * + * @param array $page Array of pages + * + * @return bool + * @access private + */ +function admin_page_handler($page) { + + admin_gatekeeper(); + elgg_admin_add_plugin_settings_menu(); + elgg_set_context('admin'); + + 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' && 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 { + $view = 'admin/' . implode('/', $page); + $title = elgg_echo("admin:{$page[0]}"); + if (count($page) > 1) { + $title .= ' : ' . elgg_echo('admin:' . implode(':', $page)); + } + } + + // 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'); + } + + $body = elgg_view_layout('admin', array('content' => $content, 'title' => $title)); + echo elgg_view_page($title, $body, 'admin'); + return true; +} + +/** + * Serves up screenshots for plugins from + * admin_plugin_screenshot/<plugin_id>/<size>/<ss_name>.<ext> + * + * @param array $pages The pages array + * @return bool + * @access private + */ +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'); + + // the rest of the string is the filename + $filename_parts = array_slice($pages, 2); + $filename = implode('/', $filename_parts); + $filename = sanitise_filepath($filename, false); + + $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'; + } + } + + 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; +} + +/** + * Formats and serves out markdown files from plugins. + * + * 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 type $page + * @return bool + * @access private + */ +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; +} + +/** + * Adds default admin widgets to the admin dashboard. + * + * @return void + * @access private + */ +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); + $widget->move($column, $position); + } + } + } + elgg_set_ignore_access(false); +} + +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 new file mode 100644 index 000000000..3b9f84703 --- /dev/null +++ b/engine/lib/annotations.php @@ -0,0 +1,579 @@ +<?php +/** + * Elgg annotations + * Functions to manage object annotations. + * + * @package Elgg + * @subpackage Core + */ + +/** + * Convert a database row to a new ElggAnnotation + * + * @param stdClass $row Db row result object + * + * @return ElggAnnotation + * @access private + */ +function row_to_elggannotation($row) { + if (!($row instanceof stdClass)) { + return $row; + } + + return new ElggAnnotation($row); +} + +/** + * Get a specific annotation by its id. + * If you want multiple annotation objects, use + * {@link elgg_get_annotations()}. + * + * @param int $id The id of the annotation object being retrieved. + * + * @return false|ElggAnnotation + */ +function elgg_get_annotation_from_id($id) { + return elgg_get_metastring_based_object_from_id($id, 'annotations'); +} + +/** + * 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 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 = 0, $access_id = ACCESS_PRIVATE) { + global $CONFIG; + + $result = false; + + $entity_guid = (int)$entity_guid; + //$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 = elgg_get_logged_in_user_guid(); + } + + $access_id = (int)$access_id; + $time = time(); + + // Add the metastring + $value = add_metastring($value); + if (!$value) { + return false; + } + + $name = add_metastring($name); + if (!$name) { + return false; + } + + $entity = get_entity($entity_guid); + + 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 = elgg_get_annotation_from_id($result); + if (elgg_trigger_event('create', 'annotation', $obj)) { + return $result; + } else { + // plugin returned false to reject annotation + elgg_delete_annotation_by_id($result); + return FALSE; + } + } + } + + return $result; +} + +/** + * Update an annotation. + * + * @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; + + $annotation_id = (int)$annotation_id; + $name = (trim($name)); + $value = (trim($value)); + $value_type = detect_extender_valuetype($value, sanitise_string(trim($value_type))); + + $owner_guid = (int)$owner_guid; + if ($owner_guid == 0) { + $owner_guid = elgg_get_logged_in_user_guid(); + } + + $access_id = (int)$access_id; + + $access = get_access_sql_suffix(); + + // Add the metastring + $value = add_metastring($value); + if (!$value) { + return false; + } + + $name = add_metastring($name); + if (!$name) { + return false; + } + + // If ok then add it + $result = update_data("UPDATE {$CONFIG->dbprefix}annotations + 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; +} + +/** + * Returns annotations. Accepts all elgg_get_entities() options for entity + * restraints. + * + * @see elgg_get_entities + * + * @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 mixed + * @since 1.8.0 + */ +function elgg_get_annotations(array $options = array()) { + $options['metastring_type'] = 'annotations'; + return elgg_get_metastring_based_objects($options); +} + +/** + * Deletes annotations based on $options. + * + * @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 array $options An options array. {@See elgg_get_annotations()} + * @return mixed Null if the metadata name is invalid. Bool on success or fail. + * @since 1.8.0 + */ +function elgg_delete_annotations(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'annotations')) { + return false; + } + + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); +} + +/** + * Disables annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_annotations()} + * @return mixed + * @since 1.8.0 + */ +function elgg_disable_annotations(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'annotations')) { + return false; + } + + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', false); +} + +/** + * Enables annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_annotations()} + * @return mixed + * @since 1.8.0 + */ +function elgg_enable_annotations(array $options) { + if (!$options || !is_array($options)) { + return false; + } + + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback'); +} + +/** + * 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()}. + * + * @return string The list of entities + * @since 1.8.0 + */ +function elgg_list_annotations($options) { + $defaults = array( + 'limit' => 25, + 'offset' => (int) max(get_input('annoff', 0), 0), + ); + + $options = array_merge($defaults, $options); + + return elgg_list_entities($options, 'elgg_get_annotations', 'elgg_view_annotation_list'); +} + +/** + * Entities interfaces + */ + +/** + * Returns entities based upon annotations. Also accepts all options available + * to elgg_get_entities() and elgg_get_entities_from_metadata(). + * + * Entity creation time is selected as maxtime. To sort based upon + * this, pass 'order_by' => 'maxtime asc' || 'maxtime desc' + * + * @see elgg_get_entities + * @see elgg_get_entities_from_metadata + * + * @param array $options Array in format: + * + * annotation_names => NULL|ARR annotations names + * + * annotation_values => NULL|ARR annotations values + * + * 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 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, + + 'annotation_name_value_pairs_operator' => 'AND', + 'annotation_case_sensitive' => TRUE, + 'order_by_annotation' => array(), + + 'annotation_created_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_created_time_upper' => ELGG_ENTITIES_ANY_VALUE, + + 'annotation_owner_guids' => ELGG_ENTITIES_ANY_VALUE, + + 'order_by' => 'maxtime desc', + 'group_by' => 'a.entity_guid' + ); + + $options = array_merge($defaults, $options); + + $singulars = array('annotation_name', 'annotation_value', + 'annotation_name_value_pair', 'annotation_owner_guid'); + + $options = elgg_normalise_plural_options_array($options, $singulars); + $options = elgg_entities_get_metastrings_options('annotation', $options); + + if (!$options) { + return false; + } + + // special sorting for annotations + //@todo overrides other sorting + $options['selects'][] = "max(n_table.time_created) as maxtime"; + $options['group_by'] = 'n_table.entity_guid'; + + $time_wheres = elgg_get_entity_time_where_sql('a', $options['annotation_created_time_upper'], + $options['annotation_created_time_lower']); + + if ($time_wheres) { + $options['wheres'] = array_merge($options['wheres'], $time_wheres); + } + + return elgg_get_entities_from_metadata($options); +} + +/** + * Returns a viewable list of entities from annotations. + * + * @param array $options Options array + * + * @see elgg_get_entities_from_annotations() + * @see elgg_list_entities() + * + * @return string + */ +function elgg_list_entities_from_annotations($options = array()) { + return elgg_list_entities($options, 'elgg_get_entities_from_annotations'); +} + +/** + * 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. + * + * '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 elgg_get_entities_from_annotation_calculation($options) { + $db_prefix = elgg_get_config('dbprefix'); + $defaults = array( + 'calculation' => 'sum', + 'order_by' => 'annotation_calculation desc' + ); + + $options = array_merge($defaults, $options); + + $function = sanitize_string(elgg_extract('calculation', $options, 'sum', false)); + + // 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"; + + // 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"; + + // don't need access control because it's taken care of by elgg_get_annotations. + $options['group_by'] = 'n_table.entity_guid'; + + $options['callback'] = 'entity_row_to_elggstar'; + + return elgg_get_annotations($options); +} + +/** + * List entities from an annotation calculation. + * + * @see elgg_get_entities_from_annotation_calculation() + * + * @param array $options An options array. + * + * @return string + */ +function elgg_list_entities_from_annotation_calculation($options) { + return elgg_list_entities($options, 'elgg_get_entities_from_annotation_calculation'); +} + +/** + * Handler called by trigger_plugin_hook on the "export" event. + * + * @param string $hook 'export' + * @param string $entity_type 'all' + * @param mixed $returnvalue Default return value + * @param mixed $params List of params to export + * + * @elgg_plugin_hook export all + * + * @return mixed + * @access private + */ +function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $params) { + // Sanity check values + if ((!is_array($params)) && (!isset($params['guid']))) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); + } + + if (!is_array($returnvalue)) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonArrayReturnValue')); + } + + $guid = (int)$params['guid']; + $name = $params['name']; + + $result = elgg_get_annotations(array( + 'guid' => $guid, + 'limit' => 0 + )); + + if ($result) { + foreach ($result as $r) { + $returnvalue[] = $r->export(); + } + } + + return $returnvalue; +} + +/** + * 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 + * + * @return mixed + */ +function get_annotation_url($id) { + $id = (int)$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 = (int)$entity_guid; + $annotation_type = sanitise_string($annotation_type); + + $sql = "select a.id" . + " FROM {$CONFIG->dbprefix}annotations a, {$CONFIG->dbprefix}metastrings m " . + " WHERE a.owner_guid={$owner_guid} AND a.entity_guid={$entity_guid} " . + " AND a.name_id=m.id AND m.string='{$annotation_type}'"; + + if ($check_annotation = 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; + } +} + +/** + * Register an annotation url handler. + * + * @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 + * @access private + */ +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'); +} + +elgg_register_event_handler('init', 'system', 'elgg_annotations_init'); diff --git a/engine/lib/cache.php b/engine/lib/cache.php new file mode 100644 index 000000000..be1c43e14 --- /dev/null +++ b/engine/lib/cache.php @@ -0,0 +1,452 @@ +<?php +/** + * Elgg cache + * Cache file interface for caching data. + * + * @package Elgg.Core + * @subpackage Cache + */ + +/* Filepath Cache */ + +/** + * Returns an ElggCache object suitable for caching system information + * + * @todo Can this be done in a cleaner way? + * @todo Swap to memcache etc? + * + * @return ElggFileCache + */ +function elgg_get_system_cache() { + global $CONFIG; + + /** + * A default filestore cache using the dataroot. + */ + static $FILE_PATH_CACHE; + + if (!$FILE_PATH_CACHE) { + $FILE_PATH_CACHE = new ElggFileCache($CONFIG->dataroot . 'system_cache/'); + } + + return $FILE_PATH_CACHE; +} + +/** + * Reset the system cache by deleting the caches + * + * @return void + */ +function elgg_reset_system_cache() { + $cache = elgg_get_system_cache(); + $cache->clear(); +} + +/** + * 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; + + if ($CONFIG->system_cache_enabled) { + $cache = elgg_get_system_cache(); + return $cache->save($type, $data); + } + + return false; +} + +/** + * 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; + + if ($CONFIG->system_cache_enabled) { + $cache = elgg_get_system_cache(); + $cached_data = $cache->load($type); + + if ($cached_data) { + return $cached_data; + } + } + + return 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; + + datalist_set('system_cache_enabled', 1); + $CONFIG->system_cache_enabled = 1; + elgg_reset_system_cache(); +} + +/** + * 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; + + datalist_set('system_cache_enabled', 0); + $CONFIG->system_cache_enabled = 0; + elgg_reset_system_cache(); +} + +/** @todo deprecate in Elgg 1.9 **/ + +/** + * @access private + */ +function elgg_get_filepath_cache() { + return elgg_get_system_cache(); +} +/** + * @access private + */ +function elgg_filepath_cache_reset() { + return 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() { + return elgg_enable_system_cache(); +} +/** + * @access private + */ +function elgg_disable_filepath_cache() { + return elgg_disable_system_cache(); +} + +/* Simplecache */ + +/** + * 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; + } + + if (!isset($CONFIG->views->simplecache)) { + $CONFIG->views->simplecache = array(); + } + + $CONFIG->views->simplecache[] = $viewname; +} + +/** + * 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 + */ +function elgg_get_simplecache_url($type, $view) { + global $CONFIG; + $lastcache = (int)$CONFIG->lastcache; + $viewtype = elgg_get_viewtype(); + 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; +} + +/** + * Regenerates the simple cache. + * + * @warning This does not invalidate the cache, but actively resets it. + * + * @param string $viewtype Optional viewtype to regenerate. Defaults to all valid viewtypes. + * + * @return void + * @see elgg_register_simplecache_view() + * @since 1.8.0 + */ +function elgg_regenerate_simplecache($viewtype = NULL) { + global $CONFIG; + + if (!isset($CONFIG->views->simplecache) || !is_array($CONFIG->views->simplecache)) { + return; + } + + $lastcached = time(); + + // @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 (!file_exists($CONFIG->dataroot . 'views_simplecache')) { + mkdir($CONFIG->dataroot . 'views_simplecache'); + } + + 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); + + 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); + } + } + + datalist_set("simplecache_lastupdate_$viewtype", $lastcached); + datalist_set("simplecache_lastcached_$viewtype", $lastcached); + } + + elgg_set_config('debug', $old_debug); + elgg_set_viewtype($original_viewtype); + + // needs to be set for links in html head + $CONFIG->lastcache = $lastcached; + + 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; + } + + return false; +} + +/** + * Enables the simple cache. + * + * @access private + * @see elgg_register_simplecache_view() + * @return void + * @since 1.8.0 + */ +function elgg_enable_simplecache() { + global $CONFIG; + + datalist_set('simplecache_enabled', 1); + $CONFIG->simplecache_enabled = 1; + elgg_regenerate_simplecache(); +} + +/** + * Disables the simple cache. + * + * @warning Simplecache is also purged when disabled. + * + * @access private + * @see elgg_register_simplecache_view() + * @return void + * @since 1.8.0 + */ +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); + } + } +} + +/** + * 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; + + if (!isset($CONFIG->views->simplecache) || !is_array($CONFIG->views->simplecache)) { + return false; + } + + $handle = opendir($CONFIG->dataroot . 'views_simplecache'); + + if (!$handle) { + return false; + } + + // remove files. + $return = true; + while (false !== ($file = readdir($handle))) { + if ($file != "." && $file != "..") { + $return &= unlink($CONFIG->dataroot . 'views_simplecache/' . $file); + } + } + closedir($handle); + + // reset cache times + $viewtypes = $CONFIG->view_types; + + if (!is_array($viewtypes)) { + return false; + } + + foreach ($viewtypes as $viewtype) { + $return &= datalist_set("simplecache_lastupdate_$viewtype", 0); + $return &= datalist_set("simplecache_lastcached_$viewtype", 0); + } + + return $return; +} + +/** + * @see elgg_reset_system_cache() + * @access private + */ +function _elgg_load_cache() { + global $CONFIG; + + $CONFIG->system_cache_loaded = false; + + $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); + + $CONFIG->system_cache_loaded = true; +} + +/** + * @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; + } + + // 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)); + } + + if ($CONFIG->system_cache_enabled && !$CONFIG->i18n_loaded_from_cache) { + reload_all_translations(); + foreach ($CONFIG->translations as $lang => $map) { + elgg_save_system_cache("$lang.php", serialize($map)); + } + } +} + +elgg_register_event_handler('ready', 'system', '_elgg_cache_init'); diff --git a/engine/lib/calendar.php b/engine/lib/calendar.php new file mode 100644 index 000000000..9a06c5292 --- /dev/null +++ b/engine/lib/calendar.php @@ -0,0 +1,571 @@ +<?php +/** + * Elgg calendar / entity / event functions. + * + * @package Elgg.Core + * @subpackage Calendar + * + * @todo Implement or remove + */ + +/** + * 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); +} + +/** + * 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); +} + +/** + * 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 + */ +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) { + 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 + */ +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); + $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 + */ +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; + $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 + */ +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); +} + +/** + * 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 + */ +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); +} + +/** + * 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 + */ +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); +} + +/** + * 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 + */ +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); + + 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 + */ +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, $listtypetoggle, $navigation); +} diff --git a/engine/lib/configuration.php b/engine/lib/configuration.php new file mode 100644 index 000000000..b10e51130 --- /dev/null +++ b/engine/lib/configuration.php @@ -0,0 +1,631 @@ +<?php +/** + * Elgg configuration procedural code. + * + * 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; + } + + 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 32 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 + * @return void + * @access private + */ +function verify_installation() { + global $CONFIG; + + if (isset($CONFIG->installed)) { + return $CONFIG->installed; + } + + 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 32 characters in db, so catch here + if (elgg_strlen($name) > 32) { + elgg_log("The name length for configuration variables cannot be greater than 32", "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 32 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 32 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; + } +} + +/** + * 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). + * + * @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; + + 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; + } + + $query = "delete from {$CONFIG->dbprefix}config where name='$name' and site_guid=$site_guid"; + return delete_data($query); +} + +/** + * Add or update a config setting. + * + * 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 0 + * @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 32 characters in db, so catch before we set + if (elgg_strlen($name) > 32) { + elgg_log("The name length for configuration variables cannot be greater than 32", "ERROR"); + return false; + } + + // Unset existing + unset_config($name, $site_guid); + + $site_guid = (int) $site_guid; + if ($site_guid == 0) { + $site_guid = (int) $CONFIG->site_id; + } + $CONFIG->$name = $value; + $value = sanitise_string(serialize($value)); + + $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 + * + * @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; + } + + if ($site_guid == 0) { + $site_guid = (int) $CONFIG->site_id; + } + + $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 null; +} + +/** + * 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; + + $site_guid = (int) $site_guid; + + if ($site_guid == 0) { + $site_guid = (int) $CONFIG->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; + $CONFIG->$name = unserialize($value); + } + + return true; + } + return false; +} + +/** + * Loads configuration related to this site + * + * This loads from the config database table and the site entity + * @access private + */ +function _elgg_load_site_config() { + global $CONFIG; + + $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')); + } + + $CONFIG->wwwroot = $CONFIG->site->url; + $CONFIG->sitename = $CONFIG->site->name; + $CONFIG->sitedescription = $CONFIG->site->description; + $CONFIG->siteemail = $CONFIG->site->email; + $CONFIG->url = $CONFIG->wwwroot; + + get_all_config(); + // gives hint to elgg_get_config function how to approach missing values + $CONFIG->site_config_loaded = true; +} + +/** + * Loads configuration related to Elgg as an application + * + * This loads from the datalists database table + * @access private + */ +function _elgg_load_application_config() { + global $CONFIG; + + $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; + } + } + + $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; + } + $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; + } + + // initialize context here so it is set before the get_input call + $CONFIG->context = array(); + + // 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; + + $CONFIG->i18n_loaded_from_cache = false; + + // this must be synced with the enum for the entities table + $CONFIG->entity_types = array('group', 'object', 'site', 'user'); +} diff --git a/engine/lib/cron.php b/engine/lib/cron.php new file mode 100644 index 000000000..f7a032f4a --- /dev/null +++ b/engine/lib/cron.php @@ -0,0 +1,91 @@ +<?php +/** + * Elgg cron library. + * + * @package Elgg + * @subpackage Core + */ + +/** + * Cron initialization + * + * @return void + * @access private + */ +function cron_init() { + // Register a pagehandler for cron + 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 + * + * @param array $page Pages + * + * @return bool + * @access private + */ +function cron_page_handler($page) { + global $CONFIG; + + 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 + $std_out = ""; + $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; +} + +elgg_register_event_handler('init', 'system', 'cron_init'); diff --git a/engine/lib/database.php b/engine/lib/database.php new file mode 100644 index 000000000..7d90b30b8 --- /dev/null +++ b/engine/lib/database.php @@ -0,0 +1,757 @@ +<?php +/** + * Elgg database procedural code. + * + * Includes functions for establishing and retrieving a database link, + * reading data, writing data, upgrading DB schemas, and sanitizing input. + * + * @package Elgg.Core + * @subpackage Database + */ + +/** + * Query cache for all queries. + * + * Each query and its results are stored in this array as: + * <code> + * $DB_QUERY_CACHE[$query] => array(result1, result2, ... resultN) + * </code> + * + * @global array $DB_QUERY_CACHE + */ +global $DB_QUERY_CACHE; +$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 + */ +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 array $dblink + */ +global $dblink; +$dblink = array(); + +/** + * Database call count + * + * Each call to the database increments this counter. + * + * @global integer $dbcalls + */ +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 The type of database connection. Used to identify the + * resource. eg "read", "write", or "readwrite". + * + * @return void + * @access private + */ +function establish_db_link($dblinkname = "readwrite") { + // Get configuration, and globalise database link + global $CONFIG, $dblink, $DB_QUERY_CACHE, $dbcalls; + + if ($dblinkname != "readwrite" && isset($CONFIG->db[$dblinkname])) { + if (is_array($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; + $dbname = $CONFIG->db[$dblinkname][$index]->dbname; + } else { + $dbhost = $CONFIG->db[$dblinkname]->dbhost; + $dbuser = $CONFIG->db[$dblinkname]->dbuser; + $dbpass = $CONFIG->db[$dblinkname]->dbpass; + $dbname = $CONFIG->db[$dblinkname]->dbname; + } + } else { + $dbhost = $CONFIG->dbhost; + $dbuser = $CONFIG->dbuser; + $dbpass = $CONFIG->dbpass; + $dbname = $CONFIG->dbname; + } + + // Connect to database + 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($dbname, $dblink[$dblinkname])) { + $msg = elgg_echo('DatabaseException:NoConnect', array($dbname)); + throw new DatabaseException($msg); + } + + // Set DB for UTF8 + mysql_query("SET NAMES utf8"); + + $db_cache_off = FALSE; + if (isset($CONFIG->db_disable_query_cache)) { + $db_cache_off = $CONFIG->db_disable_query_cache; + } + + // 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'); + } +} + +/** + * 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. + * + * @return void + * @access private + */ +function setup_db_connections() { + global $CONFIG, $dblink; + + if (!empty($CONFIG->db->split)) { + establish_db_link('read'); + establish_db_link('write'); + } else { + establish_db_link('readwrite'); + } +} + +/** + * Display profiling information about db at NOTICE debug level upon shutdown. + * + * @return void + * @access private + */ +function db_profiling_shutdown_hook() { + global $dbcalls; + + // demoted to NOTICE as it corrupts javasript at DEBUG + elgg_log("DB Queries for this page: $dbcalls", 'NOTICE'); +} + +/** + * Execute any delayed queries upon shutdown. + * + * @return void + * @access private + */ +function db_delayedexecution_shutdown_hook() { + global $DB_DELAYED_QUERIES; + + foreach ($DB_DELAYED_QUERIES as $query_details) { + 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 dealt with here + elgg_log($e, 'WARNING'); + } + } +} + +/** + * Returns (if required, also creates) a database link resource. + * + * 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". + * + * @return object Database link + * @access private + */ +function get_db_link($dblinktype) { + global $dblink; + + if (isset($dblink[$dblinktype])) { + return $dblink[$dblinktype]; + } else if (isset($dblink['readwrite'])) { + return $dblink['readwrite']; + } else { + setup_db_connections(); + return get_db_link($dblinktype); + } +} + +/** + * Execute an EXPLAIN for $query. + * + * @param str $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; +} + +/** + * Execute a 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 link $dblink The DB link + * + * @return The result of mysql_query() + * @throws DatabaseException + * @access private + */ +function execute_query($query, $dblink) { + global $CONFIG, $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 (mysql_errno($dblink)) { + throw new DatabaseException(mysql_error($dblink) . "\n\n QUERY: " . $query); + } + + return $result; +} + +/** + * 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 {@link mysql_query()}. + * + * @param string $query The query to execute + * @param resource $dblink The database link to use or the link type (read | write) + * @param string $handler A callback function to pass the results array to + * + * @return true + * @access private + */ +function execute_delayed_query($query, $dblink, $handler = "") { + global $DB_DELAYED_QUERIES; + + if (!isset($DB_DELAYED_QUERIES)) { + $DB_DELAYED_QUERIES = array(); + } + + if (!is_resource($dblink) && $dblink != 'read' && $dblink != 'write') { + return false; + } + + // Construct delayed query + $delayed_query = array(); + $delayed_query['q'] = $query; + $delayed_query['l'] = $dblink; + $delayed_query['h'] = $handler; + + $DB_DELAYED_QUERIES[] = $delayed_query; + + return TRUE; +} + +/** + * Write wrapper for execute_delayed_query() + * + * @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, 'write', $handler); +} + +/** + * Read wrapper for execute_delayed_query() + * + * @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, 'read', $handler); +} + +/** + * 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 = "") { + return elgg_query_runner($query, $callback, false); +} + +/** + * 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 $CONFIG, $DB_QUERY_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. + // http://trac.elgg.org/ticket/4049 + $hash = (string)$callback . (int)$single . $query; + + // Is cached? + if ($DB_QUERY_CACHE) { + $cached_query = $DB_QUERY_CACHE[$hash]; + + if ($cached_query !== FALSE) { + elgg_log("DB query $query results returned from cache (hash: $hash)", 'NOTICE'); + return $cached_query; + } + } + + $dblink = get_db_link('read'); + $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 interation. + $is_callable = is_callable($callback); + while ($row = mysql_fetch_object($result)) { + if ($is_callable) { + $row = $callback($row); + } + + if ($single) { + $return = $row; + break; + } else { + $return[] = $row; + } + } + } + + if (empty($return)) { + elgg_log("DB query $query returned no results.", 'NOTICE'); + } + + // Cache result + if ($DB_QUERY_CACHE) { + $DB_QUERY_CACHE[$hash] = $return; + elgg_log("DB query $query results cached (hash: $hash)", 'NOTICE'); + } + + return $return; +} + +/** + * 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. + * + * @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", 'NOTICE'); + + if (execute_query("$query", $dblink)) { + return mysql_insert_id($dblink); + } + + return FALSE; +} + +/** + * Update a row in the database. + * + * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. + * + * @param string $query The query to run. + * + * @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", 'NOTICE'); + } + + if (execute_query("$query", $dblink)) { + return TRUE; + } + + return FALSE; +} + +/** + * Remove a row from the database. + * + * @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", 'NOTICE'); + } + + if (execute_query("$query", $dblink)) { + return mysql_affected_rows($dblink); + } + + return FALSE; +} + + +/** + * Return tables matching the database prefix {@link $CONFIG->dbprefix}% in the currently + * selected database. + * + * @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; + static $tables; + + if (isset($tables)) { + return $tables; + } + + try{ + $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; + } + + $tables = array(); + + if (is_array($result) && !empty($result)) { + foreach ($result as $row) { + $row = (array) $row; + if (is_array($row) && !empty($row)) { + foreach ($row as $element) { + $tables[] = $element; + } + } + } + } else { + return FALSE; + } + + return $tables; +} + +/** + * 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); + return update_data("optimize table $table"); +} + +/** + * Get the last database error for a particular database link + * + * @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. + * + * 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} + * + * @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)) { + global $CONFIG; + + $errors = array(); + + // Remove MySQL -- style comments + $script = preg_replace('/\-\-.*\n/', '', $script); + + // 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); + if (!empty($statement)) { + try { + $result = update_data($statement); + } catch (DatabaseException $e) { + $errors[] = $e->getMessage(); + } + } + } + if (!empty($errors)) { + $errortxt = ""; + foreach ($errors as $error) { + $errortxt .= " {$error};"; + } + + $msg = elgg_echo('DatabaseException:DBSetupIssues') . $errortxt; + throw new DatabaseException($msg); + } + } else { + $msg = elgg_echo('DatabaseException:ScriptNotFound', array($scriptlocation)); + throw new DatabaseException($msg); + } +} + +/** + * Format a query string for logging + * + * @param string $query Query string + * @return string + * @access private + */ +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); + + for ($n = 0; $n < strlen($extra_escapeable); $n++) { + $string = str_replace($extra_escapeable[$n], "\\" . $extra_escapeable[$n], $string); + } + + return $string; +} + +/** + * Sanitise a string for database use. + * + * @param string $string The string to sanitise + * + * @return string Sanitised string + */ +function sanitise_string($string) { + // @todo does this really need the trim? + // there are times when you might want trailing / preceeding white space. + return mysql_real_escape_string(trim($string)); +} + +/** + * Wrapper function for alternate English spelling + * + * @param string $string The string to sanitise + * + * @return string Sanitised string + */ +function sanitize_string($string) { + return sanitise_string($string); +} + +/** + * Sanitises an integer for database use. + * + * @param int $int Value to be sanitized + * @param bool $signed Whether negative values should be allowed (true) + * @return int + */ +function sanitise_int($int, $signed = true) { + $int = (int) $int; + + if ($signed === false) { + if ($int < 0) { + $int = 0; + } + } + + return (int) $int; +} + +/** + * Sanitizes an integer for database use. + * Wrapper function for alternate English spelling (@see sanitise_int) + * + * @param int $int Value to be sanitized + * @param bool $signed Whether negative values should be allowed (true) + * @return int + */ +function sanitize_int($int, $signed = true) { + return sanitise_int($int, $signed); +} + +/** + * 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'); +} + +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..519eea89d --- /dev/null +++ b/engine/lib/deprecated-1.7.php @@ -0,0 +1,1162 @@ +<?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 + */ +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 + */ +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..4b9d41543 --- /dev/null +++ b/engine/lib/deprecated-1.8.php @@ -0,0 +1,4774 @@ +<?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, + 'types' => $entity_type, 'subtypes' => $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, + 'types' => $entity_type, + 'subtypes' => $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, + 'types' => $type, + 'subtypes' => $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, + '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); +} + +/** + * 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, + 'types' => 'collection', + 'subtypes' => $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. + */ +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 + */ +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; +} 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 new file mode 100644 index 000000000..540605876 --- /dev/null +++ b/engine/lib/elgglib.php @@ -0,0 +1,2288 @@ +<?php +/** + * Bootstrapping and helper procedural code available for use in Elgg core and plugins. + * + * @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. + */ + +// prep core classes to be autoloadable +spl_autoload_register('_elgg_autoload'); +elgg_register_classes(dirname(dirname(__FILE__)) . '/classes'); + +/** + * Autoload classes + * + * @param string $class The name of the class + * + * @return void + * @throws Exception + * @access private + */ +function _elgg_autoload($class) { + global $CONFIG; + + if (!isset($CONFIG->classes[$class]) || !include($CONFIG->classes[$class])) { + return false; + } +} + +/** + * Register all files found in $dir as classes + * Need to be named MyClass.php + * + * @param string $dir The dir to look in + * + * @return void + * @since 1.8.0 + */ +function elgg_register_classes($dir) { + $classes = elgg_get_file_list($dir, array(), array(), array('.php')); + + foreach ($classes as $class) { + elgg_register_class(basename($class, '.php'), $class); + } +} + +/** + * Register a classname to a file. + * + * @param string $class The name of the class + * @param string $location The location of the file + * + * @return true + * @since 1.8.0 + */ +function elgg_register_class($class, $location) { + global $CONFIG; + + if (!isset($CONFIG->classes)) { + $CONFIG->classes = array(); + } + + $CONFIG->classes[$class] = $location; + + return true; +} + +/** + * Register a php library. + * + * @param string $name The name of the library + * @param string $location The location of the file + * + * @return void + * @since 1.8.0 + */ +function elgg_register_library($name, $location) { + global $CONFIG; + + if (!isset($CONFIG->libraries)) { + $CONFIG->libraries = array(); + } + + $CONFIG->libraries[$name] = $location; +} + +/** + * Load a php library. + * + * @param string $name The name of the library + * + * @return void + * @throws InvalidParameterException + * @since 1.8.0 + */ +function elgg_load_library($name) { + global $CONFIG; + + if (!isset($CONFIG->libraries)) { + $CONFIG->libraries = array(); + } + + if (!isset($CONFIG->libraries[$name])) { + $error = elgg_echo('InvalidParameterException:LibraryNotRegistered', array($name)); + throw new InvalidParameterException($error); + } + + if (!include_once($CONFIG->libraries[$name])) { + $error = elgg_echo('InvalidParameterException:LibraryNotFound', array( + $name, + $CONFIG->libraries[$name]) + ); + throw new InvalidParameterException($error); + } +} + +/** + * Forward to $location. + * + * 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. + */ +function forward($location = "", $reason = 'system') { + global $CONFIG; + + if (!headers_sent()) { + if ($location === REFERER) { + $location = $_SERVER['HTTP_REFERER']; + } + + $location = elgg_normalize_url($location); + + // 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')); + } +} + +/** + * Register a JavaScript file for inclusion + * + * 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. + * + * 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. + * + * The JavaScript files can be local to the server or remote (such as + * Google's CDN). + * + * @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_register_js($name, $url, $location = 'head', $priority = null) { + return elgg_register_external_file('js', $name, $url, $location, $priority); +} + +/** + * Unregister a JavaScript file + * + * @param string $name The identifier for the JavaScript library + * + * @return bool + * @since 1.8.0 + */ +function elgg_unregister_js($name) { + return elgg_unregister_external_file('js', $name); +} + +/** + * Load a JavaScript resource on this page + * + * 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. + * + * @param string $name Identifier of the JavaScript resource + * + * @return void + * @since 1.8.0 + */ +function elgg_load_js($name) { + elgg_load_external_file('js', $name); +} + +/** + * Get the JavaScript URLs that are loaded + * + * @param string $location 'head' or 'footer' + * + * @return array + * @since 1.8.0 + */ +function elgg_get_loaded_js($location = 'head') { + return elgg_get_loaded_external_files('js', $location); +} + +/** + * Register a CSS file for inclusion in the HTML head + * + * @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_register_css($name, $url, $priority = null) { + return elgg_register_external_file('css', $name, $url, 'head', $priority); +} + +/** + * Unregister a CSS file + * + * @param string $name The identifier for the CSS file + * + * @return bool + * @since 1.8.0 + */ +function elgg_unregister_css($name) { + return elgg_unregister_external_file('css', $name); +} + +/** + * 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 elgg_load_css($name) { + elgg_load_external_file('css', $name); +} + +/** + * Get the loaded CSS URLs + * + * @return array + * @since 1.8.0 + */ +function elgg_get_loaded_css() { + return elgg_get_loaded_external_files('css', 'head'); +} + +/** + * Core registration function for external files + * + * @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 + * + * @return bool + * @since 1.8.0 + */ +function elgg_register_external_file($type, $name, $url, $location, $priority = 500) { + global $CONFIG; + + if (empty($name) || empty($url)) { + return false; + } + + $url = elgg_format_url($url); + $url = elgg_normalize_url($url); + + elgg_bootstrap_externals_data_structure($type); + + $name = trim(strtolower($name)); + + // normalize bogus priorities, but allow empty, null, and false to be defaults. + if (!is_numeric($priority)) { + $priority = 500; + } + + // no negative priorities right now. + $priority = max((int)$priority, 0); + + $item = elgg_extract($name, $CONFIG->externals_map[$type]); + + if ($item) { + // updating a registered item + // don't update loaded because it could already be set + $item->url = $url; + $item->location = $location; + + // 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 { + $item = new stdClass(); + $item->loaded = false; + $item->url = $url; + $item->location = $location; + + $priority = $CONFIG->externals[$type]->add($item, $priority); + } + + $CONFIG->externals_map[$type][$name] = $item; + + return $priority !== false; +} + +/** + * Unregister an external file + * + * @param string $type Type of file: js or css + * @param string $name The identifier of the file + * + * @return bool + * @since 1.8.0 + */ +function elgg_unregister_external_file($type, $name) { + global $CONFIG; + + elgg_bootstrap_externals_data_structure($type); + + $name = trim(strtolower($name)); + $item = elgg_extract($name, $CONFIG->externals_map[$type]); + + if ($item) { + unset($CONFIG->externals_map[$type][$name]); + return $CONFIG->externals[$type]->remove($item); + } + + return false; +} + +/** + * Load an external resource for use on this page + * + * @param string $type Type of file: js or css + * @param string $name The identifier for the file + * + * @return void + * @since 1.8.0 + */ +function elgg_load_external_file($type, $name) { + global $CONFIG; + + elgg_bootstrap_externals_data_structure($type); + + $name = trim(strtolower($name)); + + $item = elgg_extract($name, $CONFIG->externals_map[$type]); + + if ($item) { + // update a registered item + $item->loaded = true; + } else { + $item = new stdClass(); + $item->loaded = true; + $item->url = ''; + $item->location = ''; + + $priority = $CONFIG->externals[$type]->add($item); + $CONFIG->externals_map[$type][$name] = $item; + } +} + +/** + * Get external resource descriptors + * + * @param string $type Type of file: js or css + * @param string $location Page location + * + * @return array + * @since 1.8.0 + */ +function elgg_get_loaded_external_files($type, $location) { + global $CONFIG; + + if (isset($CONFIG->externals) && $CONFIG->externals[$type] instanceof ElggPriorityList) { + $items = $CONFIG->externals[$type]->getElements(); + + $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; + } + return array(); +} + +/** + * Bootstraps the externals data structure in $CONFIG. + * + * @param string $type The type of external, js or css. + * @access private + */ +function elgg_bootstrap_externals_data_structure($type) { + global $CONFIG; + + if (!isset($CONFIG->externals)) { + $CONFIG->externals = array(); + } + + if (!isset($CONFIG->externals[$type]) || !$CONFIG->externals[$type] instanceof ElggPriorityList) { + $CONFIG->externals[$type] = new ElggPriorityList(); + } + + if (!isset($CONFIG->externals_map)) { + $CONFIG->externals_map = array(); + } + + if (!isset($CONFIG->externals_map[$type])) { + $CONFIG->externals_map[$type] = array(); + } +} + +/** + * Returns a list of files in $directory. + * + * Only returns files. Does not recurse into subdirs. + * + * @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'). + * + * @return array Filenames in $directory, in the form $directory/filename. + */ +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($directory . $file) || in_array($file, $exceptions)) { + continue; + } + + if (is_array($extensions)) { + if (in_array(strrchr($file, '.'), $extensions)) { + $list[] = $directory . $file; + } + } else { + $list[] = $directory . $file; + } + } + closedir($handle); + } + + return $list; +} + +/** + * Sanitise file paths ensuring that they begin and end with slashes etc. + * + * @param string $path The path + * @param bool $append_slash Add tailing slash + * + * @return string + */ +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); + + // Sort trailing slash + $path = trim($path); + // rtrim defaults plus / + $path = rtrim($path, " \n\t\0\x0B/"); + + if ($append_slash) { + $path = $path . '/'; + } + + return $path; +} + +/** + * Queues a message to be displayed. + * + * Messages will not be displayed immediately, but are stored in + * for later display, usually upon next page load. + * + * 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. + * + * @internal Messages are stored as strings in the $_SESSION['msg'][$register] array. + * + * @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. + * + * @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 true|false|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 = "success", $count = false) { + if (!isset($_SESSION['msg'])) { + $_SESSION['msg'] = array(); + } + if (!isset($_SESSION['msg'][$register]) && !empty($register)) { + $_SESSION['msg'][$register] = array(); + } + if (!$count) { + if (!empty($message) && is_array($message)) { + $_SESSION['msg'][$register] = array_merge($_SESSION['msg'][$register], $message); + return true; + } else if (!empty($message) && is_string($message)) { + $_SESSION['msg'][$register][] = $message; + return true; + } else if (is_null($message)) { + if ($register != "") { + $returnarray = array(); + $returnarray[$register] = $_SESSION['msg'][$register]; + $_SESSION['msg'][$register] = array(); + } else { + $returnarray = $_SESSION['msg']; + $_SESSION['msg'] = array(); + } + return $returnarray; + } + } else { + if (!empty($register)) { + return sizeof($_SESSION['msg'][$register]); + } else { + $count = 0; + foreach ($_SESSION['msg'] as $register => $submessages) { + $count += sizeof($submessages); + } + return $count; + } + } + return 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); +} + +/** + * Display a system message on next page load. + * + * @see system_messages() + * + * @param string|array $message Message or messages to add + * + * @return bool + */ +function system_message($message) { + return system_messages($message, "success"); +} + +/** + * Display an error on next page load. + * + * @see system_messages() + * + * @param string|array $error Error or errors to add + * + * @return bool + */ +function register_error($error) { + return system_messages($error, "error"); +} + +/** + * 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. + * + * 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. + * + * The callback is passed 3 arguments when called: $event, $type, and optional $params. + * + * $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. + * + * @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. + * + * @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. + * + * @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. + * + * @warning If you use the 'all' keyword, you must have logic in the handler callback to + * test the passed parameters before taking an action. + * + * @tip When referring to events, the preferred syntax is "event, type". + * + * @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 + * + * @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 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(); + } + if (!isset($CONFIG->events[$event])) { + $CONFIG->events[$event] = array(); + } + if (!isset($CONFIG->events[$event][$object_type])) { + $CONFIG->events[$event][$object_type] = array(); + } + + if (!is_callable($callback, true)) { + return false; + } + + $priority = max((int) $priority, 0); + + while (isset($CONFIG->events[$event][$object_type][$priority])) { + $priority++; + } + $CONFIG->events[$event][$object_type][$priority] = $callback; + ksort($CONFIG->events[$event][$object_type]); + return true; +} + +/** + * Unregisters a callback for an event. + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $callback The callback + * + * @return void + * @since 1.7 + */ +function elgg_unregister_event_handler($event, $object_type, $callback) { + global $CONFIG; + + 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]); + } + } + } +} + +/** + * Trigger an Elgg Event and run all handler callbacks registered to that 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 in trac. + * + * @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 $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 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]; + } + 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 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()}. + * + * Similar to Elgg Events, plugin hook handler callbacks are registered by passing + * a hook, a type, and a priority. + * + * The callback is passed 4 arguments when called: $hook, $type, $value, and $params. + * + * - 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. + * + * @internal Plugin hooks are stored in $CONFIG->hooks as: + * <code> + * $CONFIG->hooks[$hook][$type][$priority] = $callback; + * </code> + * + * @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. + * + * @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. + * + * @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 callback $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 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(); + } + if (!isset($CONFIG->hooks[$hook])) { + $CONFIG->hooks[$hook] = array(); + } + if (!isset($CONFIG->hooks[$hook][$type])) { + $CONFIG->hooks[$hook][$type] = array(); + } + + 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 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 callback $callback The PHP callback to be removed + * + * @return void + * @since 1.8.0 + */ +function elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback) { + global $CONFIG; + + 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]); + } + } + } +} + +/** + * 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. + * + * $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. + * + * $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 elgg_trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) { + global $CONFIG; + + $hooks = array(); + if (isset($CONFIG->hooks[$hook][$type])) { + if ($hook != 'all' && $type != 'all') { + $hooks[] = $CONFIG->hooks[$hook][$type]; + } + } + if (isset($CONFIG->hooks['all'][$type])) { + if ($type != 'all') { + $hooks[] = $CONFIG->hooks['all'][$type]; + } + } + if (isset($CONFIG->hooks[$hook]['all'])) { + if ($hook != 'all') { + $hooks[] = $CONFIG->hooks[$hook]['all']; + } + } + if (isset($CONFIG->hooks['all']['all'])) { + $hooks[] = $CONFIG->hooks['all']['all']; + } + + 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; + } + } + } + } + } + + return $returnvalue; +} + +/** + * 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"); + } +} + +/** + * 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 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 where error occurred + * + * @return true + * @access private + * @todo Replace error_log calls with elgg_log calls. + */ +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("PHP ERROR: $error"); + register_error("ERROR: $error"); + + // Since this is a fatal error, we want to stop any further execution but do so gracefully. + throw new Exception($error); + break; + + case E_WARNING : + case E_USER_WARNING : + 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("PHP NOTICE: $error"); + } + } + + return true; +} + +/** + * 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. + * + * {@link elgg_dump()} outputs all levels but NOTICE to screen by default. + * + * @note No messages will be displayed unless debugging has been enabled. + * + * @param string $message User message + * @param string $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') { + global $CONFIG; + + // only log when debugging is enabled + if (isset($CONFIG->debug)) { + // debug to screen or log? + $to_screen = !($CONFIG->debug == 'NOTICE'); + + switch ($level) { + case 'ERROR': + // always report + elgg_dump("$level: $message", $to_screen, $level); + break; + case 'WARNING': + case 'DEBUG': + // report except if user wants only errors + if ($CONFIG->debug != 'ERROR') { + elgg_dump("$level: $message", $to_screen, $level); + } + break; + case 'NOTICE': + default: + // only report when lowest level is desired + if ($CONFIG->debug == 'NOTICE') { + elgg_dump("$level: $message", FALSE, $level); + } + break; + } + + return TRUE; + } + + return FALSE; +} + +/** + * 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. + * + * 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 + * + * @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 (!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; + } + + if ($to_screen == TRUE) { + echo '<pre>'; + print_r($value); + echo '</pre>'; + } else { + error_log(print_r($value, TRUE)); + } +} + +/** + * Sends a notice about deprecated use of a function, view, etc. + * + * 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_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 + + if (!$dep_version) { + return false; + } + + $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]; + + $dep_major_version = (int)$dep_version; + $dep_minor_version = 10 * ($dep_version - $dep_major_version); + + $visual = false; + + if (($dep_major_version < $elgg_major_version) || + ($dep_minor_version < $elgg_minor_version)) { + $visual = true; + } + + $msg = "Deprecated in $dep_major_version.$dep_minor_version: $msg"; + + if ($visual && elgg_is_admin_logged_in()) { + register_error($msg); + } + + // 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); + + foreach ($backtrace as $trace) { + $stack[] = "[#$i] {$trace['file']}:{$trace['line']}"; + $i--; + + if ($backtrace_level > 0) { + if ($backtrace_level <= 1) { + break; + } + $backtrace_level--; + } + } + + $msg .= implode("<br /> -> ", $stack); + + elgg_log($msg, 'WARNING'); + + return true; +} + +/** + * Returns the current page's complete URL. + * + * 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 current_page_url() { + global $CONFIG; + + $url = parse_url(elgg_get_site_url()); + + $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']; + } + + $page = trim($page, "/"); + + $page .= $_SERVER['REQUEST_URI']; + + 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. + // {@trac [5813]} + $quotes = array('\'', '"'); + $encoded = array('%27', '%22'); + + return $protocol . "://" . $_SERVER['SERVER_NAME'] . $port . + str_replace($quotes, $encoded, $_SERVER['REQUEST_URI']); +} + +/** + * 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? + * + * @return str Full URL + * @since 1.7.0 + */ +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 $string; + } +} + +/** + * Adds action tokens to URL + * + * 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. + * + * @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 str $url Full action URL + * @param bool $html_encode HTML encode the url? (default: false) + * + * @return str URL with action tokens + * @since 1.7.0 + * @link http://docs.elgg.org/Tutorials/Actions + */ +function elgg_add_action_tokens_to_url($url, $html_encode = FALSE) { + $components = parse_url(elgg_normalize_url($url)); + + if (isset($components['query'])) { + $query = elgg_parse_str($components['query']); + } else { + $query = array(); + } + + if (isset($query['__elgg_ts']) && isset($query['__elgg_token'])) { + return $url; + } + + // 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); + + // rebuild the full url + return elgg_http_build_url($components, $html_encode); +} + +/** + * 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); + + if (isset($url_array['query'])) { + $query = elgg_parse_str($url_array['query']); + } else { + // nothing to remove. Return original URL. + return $url; + } + + if (array_key_exists($element, $query)) { + unset($query[$element]); + } + + $url_array['query'] = http_build_query($query); + $string = elgg_http_build_url($url_array); + return $string; +} + +/** + * Adds an element or elements to a URL's query string. + * + * @param str $url The URL + * @param array $elements Key/value pairs to add to the URL + * + * @return str 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; +} + +/** + * Test if two URLs are functionally identical. + * + * @tip If $ignore_params is used, neither the name nor its value will be considered when comparing. + * + * @tip The order of GET params doesn't matter. + * + * @param string $url1 First URL + * @param string $url2 Second URL + * @param array $ignore_params GET params to ignore in the comparison + * + * @return bool + * @since 1.8.0 + */ +function elgg_http_url_is_identical($url1, $url2, $ignore_params = array('offset', 'limit')) { + global $CONFIG; + + // 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, '/'); + } + + if (elgg_substr($url1, 0, 1) == '/') { + $url2 = elgg_get_site_url() . ltrim($url2, '/'); + } + + // @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; + } + } + + // 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 (isset($url1_info['query'])) { + if ($url1_info['query'] = html_entity_decode($url1_info['query'])) { + $url1_params = elgg_parse_str($url1_info['query']); + } + } + + if (isset($url2_info['query'])) { + if ($url2_info['query'] = html_entity_decode($url2_info['query'])) { + $url2_params = elgg_parse_str($url2_info['query']); + } + } + + // 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]); + } + } + + // 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; +} + +/** + * Checks for $array[$key] and returns its value if it exists, else + * returns $default. + * + * Shorthand for $value = (isset($array['key'])) ? $array['key'] : 'default'; + * + * @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 elgg_extract($key, array $array, $default = null, $strict = true) { + if (!is_array($array)) { + return $default; + } + + if ($strict) { + return (isset($array[$key])) ? $array[$key] : $default; + } else { + return (isset($array[$key]) && !empty($array[$key])) ? $array[$key] : $default; + } +} + +/** + * 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) { + + $sort = array(); + + foreach ($array as $k => $v) { + if (isset($v[$element])) { + $sort[] = strtolower($v[$element]); + } else { + $sort[] = NULL; + } + }; + + return array_multisort($sort, $sort_order, $sort_type, $array); +} + +/** + * 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 + */ +function ini_get_bool($ini_get_arg) { + $temp = strtolower(ini_get($ini_get_arg)); + + 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 str $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; + case 'm': + $val *= 1024; + 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 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)) { + return false; + } + + return true; +} + +/** + * 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'. + * + * @return array + * @since 1.7.0 + * @access private + */ +function elgg_normalise_plural_options_array($options, $singulars) { + foreach ($singulars as $singular) { + $plural = $singular . 's'; + + 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] = $options[$singular]; + } + } + } + + unset($options[$singular]); + } + + return $options; +} + +/** + * Emits a shutdown:system event upon PHP shutdown, but before database connections are dropped. + * + * @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 _elgg_shutdown_hook() { + global $START_MICROTIME; + + 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()}"); + } +} + +/** + * 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'); +} + +/** + * Serve individual views for Ajax. + * + * /ajax/view/<name of view>?<key/value params> + * + * @param array $page The page array + * + * @return bool + * @elgg_pagehandler ajax + * @access private + */ +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; + } + + // 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; +} + +/** + * Serve CSS + * + * Serves CSS from the css views directory with headers for caching control + * + * @param array $page The page array + * + * @return void + * @elgg_pagehandler css + * @access private + */ +function elgg_css_page_handler($page) { + if (!isset($page[0])) { + // default css + $page[0] = 'elgg'; + } + + return elgg_cacheable_view_page_handler($page, 'css'); +} + +/** + * Serves a JS or CSS view with headers for caching. + * + * /<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_cacheable_view_page_handler($page, $type) { + + switch ($type) { + case 'js': + $content_type = 'text/javascript'; + break; + + case 'css': + $content_type = 'text/css'; + break; + + default: + return false; + break; + } + + 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"); + + header("Content-type: $content_type"); + + // @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)); + + echo $return; + return true; + } +} + +/** + * 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. + * + * 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_sql_reverse_order_by_clause($order_by) { + $order_by = strtolower($order_by); + + 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 { + // no order specified, so default to desc since mysql defaults to asc + $return = $order_by . ' desc'; + } + + return $return; +} + +/** + * 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; +} + +/** + * Delete objects with a delete() method. + * + * Used as a callback for ElggBatch. + * + * @param object $object The object to disable + * @return bool + * @access private + */ +function elgg_batch_delete_callback($object) { + // our db functions return the number of rows affected... + return $object->delete() ? true : false; +} + +/** + * 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; + } + + // 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; + } + + foreach ($required as $key) { + // check that it exists and is something. + if (isset($options[$key]) && $options[$key]) { + return true; + } + } + + return false; +} + +/** + * Intercepts the index page when Walled Garden mode is enabled. + * + * @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_walled_garden_index($hook, $type, $value, $params) { + if ($value) { + // do not create a second index page so return + return; + } + + 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; +} + +/** + * Checks the status of the Walled Garden and forwards to a login page + * if required. + * + * 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 elgg_walled_garden() { + global $CONFIG; + + elgg_register_css('elgg.walled_garden', '/css/walled_garden.css'); + elgg_register_js('elgg.walled_garden', '/js/walled_garden.js'); + + 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(); + } +} + +/** + * Remove public access for walled gardens + * + * @param string $hook + * @param string $type + * @param array $accesses + * @return array + * @access private + */ +function _elgg_walled_garden_remove_public_access($hook, $type, $accesses) { + if (isset($accesses[ACCESS_PUBLIC])) { + unset($accesses[ACCESS_PUBLIC]); + } + return $accesses; +} + +/** + * Boots the engine + * + * 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_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(); + + _elgg_session_boot(); + + _elgg_load_cache(); + + _elgg_load_translations(); +} + +/** + * 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_init() { + global $CONFIG; + + 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); + } + } +} + +/** + * 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 void + * @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; +} + +/**#@+ + * Controlls access levels on ElggEntity entities, metadata, and annotations. + * + * @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); + +/** + * Used in calls to forward() to specify the browser should be redirected to the + * referring page. + * + * @see forward + * @var unknown_type + */ +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 new file mode 100644 index 000000000..ce736ce05 --- /dev/null +++ b/engine/lib/entities.php @@ -0,0 +1,2526 @@ +<?php +/** + * Procedural code for creating, loading, and modifying ElggEntity objects. + * + * @package Elgg.Core + * @subpackage DataModel.Entities + * @link http://docs.elgg.org/DataModel/Entities + */ + +/** + * Cache entities in memory once loaded. + * + * @global array $ENTITY_CACHE + * @access private + */ +global $ENTITY_CACHE; +$ENTITY_CACHE = array(); + +/** + * 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; + +/** + * Invalidate this class's entry in the cache. + * + * @param int $guid The entity guid + * + * @return null + * @access private + */ +function invalidate_cache_for_entity($guid) { + global $ENTITY_CACHE; + + $guid = (int)$guid; + + unset($ENTITY_CACHE[$guid]); + + elgg_get_metadata_cache()->clear($guid); +} + +/** + * Cache an entity. + * + * Stores an entity in $ENTITY_CACHE; + * + * @param ElggEntity $entity Entity to cache + * + * @return null + * @see retrieve_cached_entity() + * @see invalidate_cache_for_entity() + * @access private + * TODO(evan): Use an ElggCache object + */ +function cache_entity(ElggEntity $entity) { + global $ENTITY_CACHE; + + // 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; + } + + // Don't store too many or we'll have memory problems + // TODO(evan): Pick a less arbitrary limit + if (count($ENTITY_CACHE) > 256) { + $random_guid = array_rand($ENTITY_CACHE); + + unset($ENTITY_CACHE[$random_guid]); + + // Purge separate metadata cache. Original idea was to do in entity destructor, but that would + // have caused a bunch of unnecessary purges at every shutdown. Doing it this way we have no way + // to know that the expunged entity will be GCed (might be another reference living), but that's + // OK; the metadata will reload if necessary. + elgg_get_metadata_cache()->clear($random_guid); + } + + $ENTITY_CACHE[$entity->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 cache_entity() + * @see invalidate_cache_for_entity() + * @access private + */ +function retrieve_cached_entity($guid) { + global $ENTITY_CACHE; + + if (isset($ENTITY_CACHE[$guid])) { + if ($ENTITY_CACHE[$guid]->isFullyLoaded()) { + return $ENTITY_CACHE[$guid]; + } + } + + return false; +} + +/** + * As retrieve_cached_entity, but returns the result as a stdClass + * (compatible with load functions that expect a database row.) + * + * @param int $guid The guid + * + * @return mixed + * @todo unused + * @access private + */ +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 id for a given subtype. + * + * ElggEntity objects have a type and a subtype. Subtypes + * are defined upon creation and cannot be changed. + * + * 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. + * + * @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 $SUBTYPE_CACHE; + + if (!$subtype) { + return false; + } + + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + + // use the cache before hitting database + $result = _elgg_retrieve_cached_subtype($type, $subtype); + if ($result !== null) { + return $result->id; + } + + return false; +} + +/** + * Return string name for a given subtype ID. + * + * @param int $subtype_id 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 $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; + } + + 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; + + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + + foreach ($SUBTYPE_CACHE as $obj) { + if ($obj->type === $type && $obj->subtype === $subtype) { + return $obj; + } + } + return null; +} + +/** + * Fetch all suptypes from DB to local cache. + * + * @access private + */ +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; + } +} + +/** + * 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; + + 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; +} + +/** + * Returns the class name for a subtype id. + * + * @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 $SUBTYPE_CACHE; + + if (!$subtype_id) { + return null; + } + + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + + if (isset($SUBTYPE_CACHE[$subtype_id])) { + return $SUBTYPE_CACHE[$subtype_id]->class; + } + + return null; +} + +/** + * 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; + } + + $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; +} + +/** + * 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 Type + * @param string $subtype Subtype + * + * @return bool + * @see add_subtype() + * @see update_subtype() + */ +function remove_subtype($type, $subtype) { + global $CONFIG; + + $type = sanitise_string($type); + $subtype = sanitise_string($subtype); + + 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 ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + + $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 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 + * + * @return bool + * @link http://docs.elgg.org/DataModel/Entities + * @access private + */ +function update_entity($guid, $owner_guid, $access_id, $container_guid = null, $time_created = null) { + global $CONFIG, $ENTITY_CACHE; + + $guid = (int)$guid; + $owner_guid = (int)$owner_guid; + $access_id = (int)$access_id; + $container_guid = (int) $container_guid; + if (is_null($container_guid)) { + $container_guid = $owner_guid; + } + $time = time(); + + $entity = get_entity($guid); + + if ($time_created == null) { + $time_created = $entity->time_created; + } else { + $time_created = (int) $time_created; + } + + 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); + } + + // 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); + } + + // Handle cases where there was no error BUT no rows were updated! + if ($ret === false) { + return false; + } + + return true; + } + } +} + +/** + * 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 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, $type = 'all', $subtype = 'all') { + $user_guid = (int)$user_guid; + $user = get_entity($user_guid); + if (!$user) { + $user = elgg_get_logged_in_user_entity(); + } + + $container_guid = (int)$container_guid; + if (!$container_guid) { + $container_guid = elgg_get_page_owner_guid(); + } + + $return = false; + + if (!$container_guid) { + $return = true; + } + + $container = get_entity($container_guid); + + if ($container) { + // If the user can edit the container, they can also write to it + if ($container->canEdit($user_guid)) { + $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) { + if ($container->isMember($user)) { + $return = true; + } + } + } + + // See if anyone else has anything to say + return elgg_trigger_plugin_hook( + 'container_permissions_check', + $type, + array( + 'container' => $container, + 'user' => $user, + 'subtype' => $subtype + ), + $return); +} + +/** + * Create a new 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) { + + global $CONFIG; + + $type = sanitise_string($type); + $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; + } + $site_guid = (int) $site_guid; + if ($container_guid == 0) { + $container_guid = $owner_guid; + } + + $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, $subtype)) { + return false; + } + } + if ($type == "") { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:EntityTypeNotSet')); + } + + return insert_data("INSERT into {$CONFIG->dbprefix}entities + (type, subtype, owner_guid, site_guid, container_guid, + access_id, time_created, time_updated, last_action) + values + ('$type',$subtype_id, $owner_guid, $site_guid, $container_guid, + $access_id, $time, $time, $time)"); +} + +/** + * Returns a database row from the entities table. + * + * @tip Use get_entity() to return the fully loaded entity. + * + * @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; + + if (!$guid) { + return false; + } + + $guid = (int) $guid; + $access = get_access_sql_suffix(); + + return get_data_row("SELECT * from {$CONFIG->dbprefix}entities where guid=$guid and $access"); +} + +/** + * Create an Elgg* object from a given entity row. + * + * 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)) { + return $row; + } + + if ((!isset($row->guid)) || (!isset($row->subtype))) { + return $row; + } + + $new_entity = false; + + // Create a memcache cache if we can + static $newentity_cache; + if ((!$newentity_cache) && (is_memcache_available())) { + $newentity_cache = new ElggMemcache('new_entity_cache'); + } + if ($newentity_cache) { + $new_entity = $newentity_cache->load($row->guid); + } + if ($new_entity) { + return $new_entity; + } + + // load class for entity if one is registered + $classname = get_subtype_class_from_id($row->subtype); + if ($classname != "") { + if (class_exists($classname)) { + $new_entity = new $classname($row); + + if (!($new_entity instanceof ElggEntity)) { + $msg = elgg_echo('ClassException:ClassnameNotClass', array($classname, 'ElggEntity')); + throw new ClassException($msg); + } + } else { + error_log(elgg_echo('ClassNotFoundException:MissingClass', array($classname))); + } + } + + if (!$new_entity) { + //@todo Make this into a function + switch ($row->type) { + case 'object' : + $new_entity = new ElggObject($row); + break; + case 'user' : + $new_entity = new ElggUser($row); + break; + case 'group' : + $new_entity = new ElggGroup($row); + break; + case 'site' : + $new_entity = new ElggSite($row); + break; + default: + $msg = elgg_echo('InstallationException:TypeNotSupported', array($row->type)); + throw new InstallationException($msg); + } + } + + // Cache entity if we have a cache available + if (($newentity_cache) && ($new_entity)) { + $newentity_cache->save($new_entity->guid, $new_entity); + } + + return $new_entity; +} + +/** + * Loads and returns an entity object from a guid. + * + * @param int $guid The GUID of the entity + * + * @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) { + // 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 = retrieve_cached_entity($guid); + if ($new_entity) { + return $new_entity; + } + + // 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; + } + } + + // 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 http://trac.elgg.org/ticket/3018#comment:3 + 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; + } + + if ($new_entity) { + 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; + } +} + +/** + * 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 (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. + * + * type_subtype_pairs => NULL|ARR (array('type' => 'subtype')) + * (type = '$type' AND subtype = '$subtype') pairs + * + * guids => NULL|ARR Array of entity guids + * + * owner_guids => NULL|ARR Array of owner guids + * + * container_guids => NULL|ARR Array of container_guids + * + * site_guids => NULL (current_site)|ARR Array of site_guid + * + * order_by => NULL (time_created desc)|STR SQL order by 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 + * + * 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 + * + * count => TRUE|FALSE return a count instead of entities + * + * wheres => array() Additional where clauses to AND together + * + * joins => array() Additional joins + * + * 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; + + $defaults = array( + 'types' => ELGG_ENTITIES_ANY_VALUE, + 'subtypes' => ELGG_ENTITIES_ANY_VALUE, + 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE, + + 'guids' => ELGG_ENTITIES_ANY_VALUE, + 'owner_guids' => ELGG_ENTITIES_ANY_VALUE, + 'container_guids' => ELGG_ENTITIES_ANY_VALUE, + 'site_guids' => $CONFIG->site_guid, + + 'modified_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'modified_time_upper' => ELGG_ENTITIES_ANY_VALUE, + 'created_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'created_time_upper' => ELGG_ENTITIES_ANY_VALUE, + + 'reverse_order_by' => false, + 'order_by' => 'e.time_created desc', + 'group_by' => ELGG_ENTITIES_ANY_VALUE, + 'limit' => 10, + 'offset' => 0, + 'count' => FALSE, + 'selects' => array(), + 'wheres' => array(), + 'joins' => array(), + + 'callback' => 'entity_row_to_elggstar', + ); + + $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', 'guid', 'owner_guid', 'container_guid', 'site_guid'); + $options = elgg_normalise_plural_options_array($options, $singulars); + + // evaluate where clauses + if (!is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } + + $wheres = $options['wheres']; + + $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']); + + // 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']); + } + + // remove identical join clauses + $joins = array_unique($options['joins']); + + foreach ($joins as $i => $join) { + if ($join === FALSE) { + return FALSE; + } elseif (empty($join)) { + unset($joins[$i]); + } + } + + // evalutate selects + if ($options['selects']) { + $selects = ''; + foreach ($options['selects'] as $select) { + $selects .= ", $select"; + } + } else { + $selects = ''; + } + + if (!$options['count']) { + $query = "SELECT DISTINCT e.*{$selects} FROM {$CONFIG->dbprefix}entities e "; + } else { + $query = "SELECT count(DISTINCT e.guid) as total FROM {$CONFIG->dbprefix}entities e "; + } + + // 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 ($options['reverse_order_by']) { + $options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by']); + } + + if (!$options['count']) { + if ($options['group_by']) { + $query .= " GROUP BY {$options['group_by']}"; + } + + if ($options['order_by']) { + $query .= " ORDER BY {$options['order_by']}"; + } + + if ($options['limit']) { + $limit = sanitise_int($options['limit'], false); + $offset = sanitise_int($options['offset'], false); + $query .= " LIMIT $offset, $limit"; + } + + if ($options['callback'] === 'entity_row_to_elggstar') { + $dt = _elgg_fetch_entities_from_sql($query); + } 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) { + 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); + + if ($guids) { + elgg_get_metadata_cache()->populateFromEntities($guids); + } + } + return $dt; + } else { + $total = get_data_row($query); + return (int)$total->total; + } +} + +/** + * Return entities from an SQL query generated by elgg_get_entities. + * + * @param string $sql + * @return ElggEntity[] + * + * @access private + * @throws LogicException + */ +function _elgg_fetch_entities_from_sql($sql) { + 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', + ); + + $rows = get_data($sql); + + // guids to look up in each type + $lookup_types = array(); + // maps GUIDs to the $rows key + $guid_to_key = array(); + + if (isset($rows[0]->type, $rows[0]->subtype) + && $rows[0]->type === 'object' + && $rows[0]->subtype == $plugin_subtype) { + // Likely the entire resultset is plugins, which have already been optimized + // to JOIN the secondary table. In this case we allow retrieving from cache, + // but abandon the extra queries. + $types_to_optimize = array(); + } + + // First pass: use cache where possible, gather GUIDs that we're optimizing + foreach ($rows as $i => $row) { + if (empty($row->guid) || empty($row->type)) { + throw new LogicException('Entity row missing guid or type'); + } + if ($entity = 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); + } + } + } + // 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]); + } + } + } + return $rows; +} + +/** + * 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 + * + * @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. + if ($subtypes && !$types) { + elgg_log("Cannot set subtypes without type.", 'WARNING'); + return FALSE; + } + + // short circuit if nothing is requested + if (!$types && !$subtypes && !$pairs) { + return ''; + } + + // these are the only valid types for entities in elgg + $valid_types = elgg_get_config('entity_types'); + + // pairs override + $wheres = array(); + if (!is_array($pairs)) { + if (!is_array($types)) { + $types = array($types); + } + + if ($subtypes && !is_array($subtypes)) { + $subtypes = array($subtypes); + } + + // decrementer for valid types. Return FALSE if no valid types + $valid_types_count = count($types); + $valid_subtypes_count = 0; + // remove invalid types to get an accurate count of + // valid types for the invalid subtype detection to use + // below. + // also grab the count of ALL subtypes on valid types to decrement later on + // and check against. + // + // yes this is duplicating a foreach on $types. + foreach ($types as $type) { + if (!in_array($type, $valid_types)) { + $valid_types_count--; + unset($types[array_search($type, $types)]); + } else { + // do the checking (and decrementing) in the subtype section. + $valid_subtypes_count += count($subtypes); + } + } + + // return false if nothing is valid. + if (!$valid_types_count) { + return FALSE; + } + + // subtypes are based upon types, so we need to look at each + // type individually to get the right subtype id. + foreach ($types as $type) { + $subtype_ids = array(); + if ($subtypes) { + 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!", 'NOTICE'); + continue; + } + } + + // return false if we're all invalid subtypes in the only valid type + if ($valid_subtypes_count <= 0) { + return FALSE; + } + } + + if (is_array($subtype_ids) && count($subtype_ids)) { + $subtype_ids_str = implode(',', $subtype_ids); + $wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))"; + } else { + $wheres[] = "({$table}.type = '$type')"; + } + } + } else { + // using type/subtype pairs + $valid_pairs_count = count($pairs); + $valid_pairs_subtypes_count = 0; + + // same deal as above--we need to know how many valid types + // and subtypes we have before hitting the subtype section. + // also normalize the subtypes into arrays here. + foreach ($pairs as $paired_type => $paired_subtypes) { + if (!in_array($paired_type, $valid_types)) { + $valid_pairs_count--; + unset($pairs[array_search($paired_type, $pairs)]); + } else { + if ($paired_subtypes && !is_array($paired_subtypes)) { + $pairs[$paired_type] = array($paired_subtypes); + } + $valid_pairs_subtypes_count += count($paired_subtypes); + } + } + + if ($valid_pairs_count <= 0) { + return FALSE; + } + foreach ($pairs as $paired_type => $paired_subtypes) { + // this will always be an array because of line 2027, right? + // no...some overly clever person can say pair => array('object' => null) + if (is_array($paired_subtypes)) { + $paired_subtype_ids = array(); + foreach ($paired_subtypes as $paired_subtype) { + if (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!", 'NOTICE'); + // return false if we're all invalid subtypes in the only valid type + continue; + } + } + + // return false if there are no valid subtypes. + if ($valid_pairs_subtypes_count <= 0) { + return FALSE; + } + + + if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) { + $wheres[] = "({$table}.type = '$paired_type'" + . " AND {$table}.subtype IN ($paired_subtype_ids_str))"; + } + } else { + $wheres[] = "({$table}.type = '$paired_type')"; + } + } + } + + // pairs override the above. return false if they don't exist. + if (is_array($wheres) && count($wheres)) { + $where = implode(' OR ', $wheres); + return "($where)"; + } + + return ''; +} + +/** + * 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. + * + * @return false|string + * @since 1.8.0 + * @access private + */ +function elgg_get_guid_based_where_sql($column, $guids) { + // short circuit if nothing requested + // 0 is a valid guid + if (!$guids && $guids !== 0) { + return ''; + } + + // normalize and sanitise owners + if (!is_array($guids)) { + $guids = array($guids); + } + + $guids_sanitized = array(); + foreach ($guids as $guid) { + if ($guid !== ELGG_ENTITIES_NO_VALUE) { + $guid = sanitise_int($guid); + + if (!$guid) { + return false; + } + } + $guids_sanitized[] = $guid; + } + + $where = ''; + $guid_str = implode(',', $guids_sanitized); + + // implode(',', 0) returns 0. + if ($guid_str !== FALSE && $guid_str !== '') { + $where = "($column IN ($guid_str))"; + } + + return $where; +} + +/** + * Returns SQL where clause for entity time limits. + * + * @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|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) { + + $wheres = array(); + + // exploit PHP's loose typing (quack) to check that they are INTs and not str cast to 0 + if ($time_created_upper && $time_created_upper == sanitise_int($time_created_upper)) { + $wheres[] = "{$table}.time_created <= $time_created_upper"; + } + + if ($time_created_lower && $time_created_lower == sanitise_int($time_created_lower)) { + $wheres[] = "{$table}.time_created >= $time_created_lower"; + } + + if ($time_updated_upper && $time_updated_upper == sanitise_int($time_updated_upper)) { + $wheres[] = "{$table}.time_updated <= $time_updated_upper"; + } + + if ($time_updated_lower && $time_updated_lower == sanitise_int($time_updated_lower)) { + $wheres[] = "{$table}.time_updated >= $time_updated_lower"; + } + + if (is_array($wheres) && count($wheres) > 0) { + $where_str = implode(' AND ', $wheres); + return "($where_str)"; + } + + return ''; +} + +/** + * Returns a string of parsed entities. + * + * Displays list of entities with formatting specified + * by the entity view. + * + * @tip Pagination is handled automatically. + * + * @internal This also provides the views for elgg_view_annotation(). + * + * @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 + * + * @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 string + * @since 1.7 + * @see elgg_get_entities() + * @see elgg_view_entity_list() + * @link http://docs.elgg.org/Entities/Output + */ +function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entities', + $viewer = 'elgg_view_entity_list') { + + global $autofeed; + $autofeed = true; + + $defaults = array( + 'offset' => (int) max(get_input('offset', 0), 0), + 'limit' => (int) max(get_input('limit', 10), 0), + 'full_view' => TRUE, + 'list_type_toggle' => FALSE, + 'pagination' => TRUE, + ); + + $options = array_merge($defaults, $options); + + //backwards compatibility + if (isset($options['view_type_toggle'])) { + $options['list_type_toggle'] = $options['view_type_toggle']; + } + + $options['count'] = TRUE; + $count = $getter($options); + + $options['count'] = FALSE; + $entities = $getter($options); + + $options['count'] = $count; + + return $viewer($entities, $options); +} + +/** + * 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. + * + * @warning Months are returned in the form YYYYMM. + * + * @param string $type The type of entity + * @param string $subtype The subtype of entity + * @param int $container_guid The container GUID that the entinties belong to + * @param int $site_guid The site GUID + * @param string $order_by Order_by SQL order by clause + * + * @return array|false Either an array months as YYYYMM, or false on failure + */ +function get_entity_dates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0, +$order_by = 'time_created') { + + global $CONFIG; + + $site_guid = (int) $site_guid; + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + $where = array(); + + if ($type != "") { + $type = sanitise_string($type); + $where[] = "type='$type'"; + } + + if (is_array($subtype)) { + $tempwhere = ""; + if (sizeof($subtype)) { + foreach ($subtype 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 .= "(type = '{$typekey}' and subtype = {$subtypeval})"; + } + } + } + if (!empty($tempwhere)) { + $where[] = "({$tempwhere})"; + } + } else { + if ($subtype) { + if (!$subtype_id = get_subtype_id($type, $subtype)) { + return FALSE; + } else { + $where[] = "subtype=$subtype_id"; + } + } + } + + if ($container_guid !== 0) { + if (is_array($container_guid)) { + foreach ($container_guid as $key => $val) { + $container_guid[$key] = (int) $val; + } + $where[] = "container_guid in (" . implode(",", $container_guid) . ")"; + } else { + $container_guid = (int) $container_guid; + $where[] = "container_guid = {$container_guid}"; + } + } + + if ($site_guid > 0) { + $where[] = "site_guid = {$site_guid}"; + } + + $where[] = get_access_sql_suffix(); + + $sql = "SELECT DISTINCT EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time_created)) AS yearmonth + FROM {$CONFIG->dbprefix}entities where "; + + foreach ($where as $w) { + $sql .= " $w and "; + } + + $sql .= "1=1 ORDER BY $order_by"; + if ($result = get_data($sql)) { + $endresult = array(); + foreach ($result as $res) { + $endresult[] = $res->yearmonth; + } + return $endresult; + } + return false; +} + +/** + * Disable an entity. + * + * 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; + + $guid = (int)$guid; + $reason = sanitise_string($reason); + + if ($entity = get_entity($guid)) { + if (elgg_trigger_event('disable', $entity->type, $entity)) { + if ($entity->canEdit()) { + if ($reason) { + create_metadata($guid, 'disable_reason', $reason, '', 0, ACCESS_PUBLIC); + } + + if ($recursive) { + $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); + } + } + access_show_hidden_entities($hidden); + elgg_set_ignore_access($ia); + } + + $entity->disableMetadata(); + $entity->disableAnnotations(); + invalidate_cache_for_entity($guid); + + $res = update_data("UPDATE {$CONFIG->dbprefix}entities + SET enabled = 'no' + WHERE guid = $guid"); + + return $res; + } + } + } + return false; +} + +/** + * Enable an entity. + * + * @warning In order to enable an entity using ElggEntity::enable(), + * 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, $recursive = true) { + global $CONFIG; + + $guid = (int)$guid; + + // Override access only visible entities + $old_access_status = access_get_show_hidden_status(); + access_show_hidden_entities(true); + + $result = false; + if ($entity = get_entity($guid)) { + if (elgg_trigger_event('enable', $entity->type, $entity)) { + if ($entity->canEdit()) { + + $result = update_data("UPDATE {$CONFIG->dbprefix}entities + SET enabled = 'yes' + WHERE guid = $guid"); + + $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($old_access_status); + return $result; +} + +/** + * 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. + * + * @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 (elgg_trigger_event('delete', $entity->type, $entity)) { + if ($entity->canEdit()) { + + // delete cache + if (isset($ENTITY_CACHE[$guid])) { + 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. + static $__RECURSIVE_DELETE_TOKEN; + // Make it slightly harder to guess + $__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); + } + + // Now delete the entity itself + $entity->deleteMetadata(); + $entity->deleteOwnedMetadata(); + $entity->deleteAnnotations(); + $entity->deleteOwnedAnnotations(); + $entity->deleteRelationships(); + + 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 = ""; + + // Where appropriate delete the sub table + switch ($entity->type) { + case 'object' : + $sub_table = $CONFIG->dbprefix . 'objects_entity'; + break; + case 'user' : + $sub_table = $CONFIG->dbprefix . 'users_entity'; + break; + case 'group' : + $sub_table = $CONFIG->dbprefix . 'groups_entity'; + break; + case 'site' : + $sub_table = $CONFIG->dbprefix . 'sites_entity'; + break; + } + + if ($sub_table) { + delete_data("DELETE from $sub_table where guid={$guid}"); + } + } + + return (bool)$res; + } + } + } + return false; + +} + +/** + * 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']; + $variable_name = sanitise_string($params['varname']); + + if (($hook == 'volatile') && ($entity_type == 'metadata')) { + if (($guid) && ($variable_name)) { + switch ($variable_name) { + case 'renderedentity' : + elgg_set_viewtype('default'); + $view = elgg_view_entity(get_entity($guid)); + elgg_set_viewtype(); + + $tmp = new ElggMetadata(); + $tmp->type = 'volatile'; + $tmp->name = 'renderedentity'; + $tmp->value = $view; + $tmp->entity_guid = $guid; + + return $tmp; + + break; + } + } + } +} + +/** + * 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 + if ((!is_array($params)) && (!isset($params['guid']))) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); + } + + if (!is_array($returnvalue)) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonArrayReturnValue')); + } + + $guid = (int)$params['guid']; + + // Get the entity + $entity = get_entity($guid); + if (!($entity instanceof ElggEntity)) { + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class())); + throw new InvalidClassException($msg); + } + + $export = $entity->export(); + + if (is_array($export)) { + foreach ($export as $e) { + $returnvalue[] = $e; + } + } else { + $returnvalue[] = $export; + } + + return $returnvalue; +} + +/** + * Utility function used by import_entity_plugin_hook() to + * process an ODDEntity into an unsaved ElggEntity. + * + * @param ODDEntity $element The OpenDD element + * + * @return ElggEntity the unsaved entity which should be populated by items. + * @todo Remove this. + * @access private + * + * @throws ClassException|InstallationException|ImportException + */ +function oddentity_to_elggentity(ODDEntity $element) { + $class = $element->getAttribute('class'); + $subclass = $element->getAttribute('subclass'); + + // See if we already have imported this uuid + $tmp = get_entity_from_uuid($element->getAttribute('uuid')); + + if (!$tmp) { + // Construct new class with owner from session + $classname = get_subtype_class($class, $subclass); + if ($classname) { + if (class_exists($classname)) { + $tmp = new $classname(); + + if (!($tmp instanceof ElggEntity)) { + $msg = elgg_echo('ClassException:ClassnameNotClass', array($classname, get_class())); + throw new ClassException($msg); + } + } else { + error_log(elgg_echo('ClassNotFoundException:MissingClass', array($classname))); + } + } else { + switch ($class) { + case 'object' : + $tmp = new ElggObject($row); + break; + case 'user' : + $tmp = new ElggUser($row); + break; + case 'group' : + $tmp = new ElggGroup($row); + break; + case 'site' : + $tmp = new ElggSite($row); + break; + default: + $msg = elgg_echo('InstallationException:TypeNotSupported', array($class)); + throw new InstallationException($msg); + } + } + } + + if ($tmp) { + if (!$tmp->import($element)) { + $msg = elgg_echo('ImportException:ImportFailed', array($element->getAttribute('uuid'))); + throw new ImportException($msg); + } + + return $tmp; + } + + return NULL; +} + +/** + * Import an entity. + * + * This function checks the passed XML doc (as array) to see if it is + * a user, if so it constructs a new elgg user and returns "true" + * to inform the importer that it's been handled. + * + * @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; + + if ($element instanceof ODDEntity) { + $tmp = oddentity_to_elggentity($element); + + if ($tmp) { + // Make sure its saved + if (!$tmp->save()) { + $msg = elgg_echo('ImportException:ProblemSaving', array($element->getAttribute('uuid'))); + throw new ImportException($msg); + } + + // Belts and braces + if (!$tmp->guid) { + throw new ImportException(elgg_echo('ImportException:NoGUID')); + } + + // We have saved, so now tag + add_uuid_to_guid($tmp->guid, $element->getAttribute('uuid')); + + return $tmp; + } + } +} + +/** + * Returns if $user_guid is able to edit $entity_guid. + * + * @tip Can be overridden by by registering for the permissions_check + * 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 bool + * @link http://docs.elgg.org/Entities/AccessControl + */ +function can_edit_entity($entity_guid, $user_guid = 0) { + $user_guid = (int)$user_guid; + $user = get_entity($user_guid); + if (!$user) { + $user = elgg_get_logged_in_user_entity(); + } + + $return = false; + if ($entity = get_entity($entity_guid)) { + + // Test user if possible - should default to false unless a plugin hook says otherwise + if ($user) { + if ($entity->getOwnerGUID() == $user->getGUID()) { + $return = true; + } + if ($entity->container_guid == $user->getGUID()) { + $return = true; + } + if ($entity->type == "user" && $entity->getGUID() == $user->getGUID()) { + $return = true; + } + if ($container_entity = get_entity($entity->container_guid)) { + if ($container_entity->canEdit($user->getGUID())) { + $return = true; + } + } + } + } + + return elgg_trigger_plugin_hook('permissions_check', $entity->type, + array('entity' => $entity, 'user' => $user), $return); +} + +/** + * Returns if $user_guid can edit the metadata on $entity_guid. + * + * @tip Can be overridden by by registering for the permissions_check:metadata + * 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 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) { + $return = true; + } + if (is_null($return)) { + $return = can_edit_entity($entity_guid, $user_guid); + } + + 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; + } +} + +/** + * Returns the URL for an entity. + * + * @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; + + if ($entity = get_entity($entity_guid)) { + $url = ""; + + if (isset($CONFIG->entity_url_handler[$entity->getType()][$entity->getSubType()])) { + $function = $CONFIG->entity_url_handler[$entity->getType()][$entity->getSubType()]; + if (is_callable($function)) { + $url = call_user_func($function, $entity); + } + } elseif (isset($CONFIG->entity_url_handler[$entity->getType()]['all'])) { + $function = $CONFIG->entity_url_handler[$entity->getType()]['all']; + if (is_callable($function)) { + $url = call_user_func($function, $entity); + } + } elseif (isset($CONFIG->entity_url_handler['all']['all'])) { + $function = $CONFIG->entity_url_handler['all']['all']; + if (is_callable($function)) { + $url = call_user_func($function, $entity); + } + } + + if ($url == "") { + $url = "view/" . $entity_guid; + } + + return elgg_normalize_url($url); + } + + return false; +} + +/** + * Sets the URL handler for a particular entity type and subtype + * + * @param string $entity_type The entity type + * @param string $entity_subtype The entity subtype + * @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 elgg_register_entity_url_handler($entity_type, $entity_subtype, $function_name) { + global $CONFIG; + + if (!is_callable($function_name, true)) { + return false; + } + + if (!isset($CONFIG->entity_url_handler)) { + $CONFIG->entity_url_handler = array(); + } + + if (!isset($CONFIG->entity_url_handler[$entity_type])) { + $CONFIG->entity_url_handler[$entity_type] = array(); + } + + $CONFIG->entity_url_handler[$entity_type][$entity_subtype] = $function_name; + + return true; +} + +/** + * 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 elgg_register_entity_type($type, $subtype = null) { + global $CONFIG; + + $type = strtolower($type); + if (!in_array($type, $CONFIG->entity_types)) { + return FALSE; + } + + if (!isset($CONFIG->registered_entities)) { + $CONFIG->registered_entities = array(); + } + + if (!isset($CONFIG->registered_entities[$type])) { + $CONFIG->registered_entities[$type] = array(); + } + + if ($subtype) { + $CONFIG->registered_entities[$type][] = $subtype; + } + + return TRUE; +} + +/** + * 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 $subtype The subtype to register (may be blank) + * + * @return bool Depending on success + * @see elgg_register_entity_type() + */ +function unregister_entity_type($type, $subtype) { + global $CONFIG; + + $type = strtolower($type); + if (!in_array($type, $CONFIG->entity_types)) { + return FALSE; + } + + if (!isset($CONFIG->registered_entities)) { + return FALSE; + } + + if (!isset($CONFIG->registered_entities[$type])) { + return FALSE; + } + + if ($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; +} + +/** + * Returns registered entity types and subtypes + * + * @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 = null) { + global $CONFIG; + + if (!isset($CONFIG->registered_entities)) { + return false; + } + if ($type) { + $type = strtolower($type); + } + if (!empty($type) && empty($CONFIG->registered_entities[$type])) { + return false; + } + + if (empty($type)) { + return $CONFIG->registered_entities; + } + + return $CONFIG->registered_entities[$type]; +} + +/** + * 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 $subtype The subtype (may be blank) + * + * @return bool Depending on whether or not the type has been registered + */ +function is_registered_entity_type($type, $subtype = null) { + global $CONFIG; + + if (!isset($CONFIG->registered_entities)) { + return false; + } + + $type = strtolower($type); + + // @todo registering a subtype implicitly registers the type. + // see #2684 + if (!isset($CONFIG->registered_entities[$type])) { + return false; + } + + 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 . "pages/entities/index.php"); + return true; + } + return false; +} + +/** + * Returns a viewable list of entities based on the registered types. + * + * @see elgg_view_entity_list + * + * @param array $options Any elgg_get_entity() options plus: + * + * full_view => BOOL Display full view entities + * + * 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(array $options = array()) { + global $autofeed; + $autofeed = true; + + $defaults = array( + 'full_view' => TRUE, + 'allowed_types' => TRUE, + 'list_type_toggle' => FALSE, + 'pagination' => TRUE, + 'offset' => 0, + 'types' => array(), + 'type_subtype_pairs' => array() + ); + + $options = array_merge($defaults, $options); + + //backwards compatibility + if (isset($options['view_type_toggle'])) { + $options['list_type_toggle'] = $options['view_type_toggle']; + } + + $types = get_registered_entity_types(); + + 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; + } + } 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($options['type_subtype_pairs'])) { + $count = elgg_get_entities(array_merge(array('count' => TRUE), $options)); + $entities = elgg_get_entities($options); + } else { + $count = 0; + $entities = array(); + } + + $options['count'] = $count; + return elgg_view_entity_list($entities, $options); +} + +/** + * Checks if $entity is an ElggEntity and optionally for type and subtype. + * + * @tip Use this function in actions and views to check that you are dealing + * with the correct type of entity. + * + * @param mixed $entity Entity + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param string $class Class name + * + * @return bool + * @since 1.8.0 + */ +function elgg_instanceof($entity, $type = NULL, $subtype = NULL, $class = NULL) { + $return = ($entity instanceof ElggEntity); + + if ($type) { + $return = $return && ($entity->getType() == $type); + } + + if ($subtype) { + $return = $return && ($entity->getSubtype() == $subtype); + } + + if ($class) { + $return = $return && ($entity instanceof $class); + } + + return $return; +} + +/** + * Update the last_action column in the entities table for $guid. + * + * @warning This is different to time_updated. Time_updated is automatically set, + * while last_action is only set when explicitly called. + * + * @param int $guid Entity annotation|relationship action carried out on + * @param int $posted Timestamp of last action + * + * @return bool + * @access private + */ +function update_entity_last_action($guid, $posted = NULL) { + global $CONFIG; + $guid = (int)$guid; + $posted = (int)$posted; + + if (!$posted) { + $posted = time(); + } + + 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 + * + * @return void + * @elgg_plugin_hook_handler gc system + * @access private + */ +function entities_gc() { + global $CONFIG; + + $tables = array ('sites_entity', 'objects_entity', 'groups_entity', 'users_entity'); + + foreach ($tables as $table) { + delete_data("DELETE from {$CONFIG->dbprefix}{$table} + where guid NOT IN (SELECT guid from {$CONFIG->dbprefix}entities)"); + } +} + +/** + * 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; + $value[] = $CONFIG->path . 'engine/tests/objects/entities.php'; + return $value; +} + +/** + * Entities init function; establishes the default entity page handler + * + * @return void + * @elgg_event_handler init system + * @access private + */ +function entities_init() { + elgg_register_page_handler('view', 'entities_page_handler'); + + elgg_register_plugin_hook_handler('unit_test', 'system', 'entities_test'); + + elgg_register_plugin_hook_handler('gc', 'system', 'entities_gc'); +} + +/** Register the import hook */ +elgg_register_plugin_hook_handler("import", "all", "import_entity_plugin_hook", 0); + +/** Register the hook, ensuring entities are serialised first */ +elgg_register_plugin_hook_handler("export", "all", "export_entity_plugin_hook", 0); + +/** Hook to get certain named bits of volatile data about an entity */ +elgg_register_plugin_hook_handler('volatile', 'metadata', 'volatile_data_export_plugin_hook'); + +/** Register init system event **/ +elgg_register_event_handler('init', 'system', 'entities_init'); + diff --git a/engine/lib/export.php b/engine/lib/export.php new file mode 100644 index 000000000..ae9be95ce --- /dev/null +++ b/engine/lib/export.php @@ -0,0 +1,226 @@ +<?php +/** + * Elgg Data import export functionality. + * + * @package Elgg.Core + * @subpackage DataModel.Export + */ + +/** + * Get a UUID from a given object. + * + * @param mixed $object The object either an ElggEntity, ElggRelationship or ElggExtender + * + * @return the UUID or false + */ +function get_uuid_from_object($object) { + if ($object instanceof ElggEntity) { + return guid_to_uuid($object->guid); + } else if ($object instanceof ElggExtender) { + $type = $object->type; + if ($type == 'volatile') { + $uuid = guid_to_uuid($object->entity_guid) . $type . "/{$object->name}/"; + } else { + $uuid = guid_to_uuid($object->entity_guid) . $type . "/{$object->id}/"; + } + + return $uuid; + } else if ($object instanceof ElggRelationship) { + return guid_to_uuid($object->guid_one) . "relationship/{$object->id}/"; + } + + return false; +} + +/** + * 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 elgg_get_site_url() . "export/opendd/$guid/"; +} + +/** + * Test to see if a given uuid is for this domain, returning true if so. + * + * @param string $uuid A unique ID + * + * @return bool + */ +function is_uuid_this_domain($uuid) { + global $CONFIG; + + if (strpos($uuid, elgg_get_site_url()) === 0) { + return true; + } + + return false; +} + +/** + * This function attempts to retrieve a previously imported entity via its UUID. + * + * @param string $uuid A unique ID + * + * @return mixed + */ +function get_entity_from_uuid($uuid) { + $uuid = sanitise_string($uuid); + + $options = array('metadata_name' => 'import_uuid', 'metadata_value' => $uuid); + $entities = elgg_get_entities_from_metadata($options); + + if ($entities) { + return $entities[0]; + } + + return false; +} + +/** + * Tag a previously created guid with the uuid it was imported on. + * + * @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); + + $result = create_metadata($guid, "import_uuid", $uuid); + return (bool)$result; +} + + +$IMPORTED_DATA = array(); +$IMPORTED_OBJECT_COUNTER = 0; + +/** + * This function processes an element, passing elements to the plugin stack to see if someone will + * process it. + * + * 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) { + global $IMPORTED_DATA, $IMPORTED_OBJECT_COUNTER; + + // See if anyone handles this element, return true if it is. + if ($odd) { + $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; + + 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; + + // Trigger a hook to + $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(elgg_echo('ExportException:NoSuchEntity', array($guid))); + } + + return $to_be_serialised; +} + +/** + * Export a GUID. + * + * This function exports a GUID and all information related to it in an XML format. + * + * This function makes use of the "serialise" plugin hook, which is passed an array to which plugins + * should add data to be serialised to. + * + * @param int $guid The GUID. + * + * @return xml + * @see ElggEntity for an example of its usage. + * @access private + */ +function export($guid) { + $odd = new ODDDocument(exportAsArray($guid)); + + return ODD_Export($odd); +} + +/** + * Import an XML serialisation of an object. + * This will make a best attempt at importing a given xml doc. + * + * @param string $xml XML string + * + * @return bool + * @throws Exception if there was a problem importing the data. + * @access private + */ +function import($xml) { + global $IMPORTED_DATA, $IMPORTED_OBJECT_COUNTER; + + $IMPORTED_DATA = array(); + $IMPORTED_OBJECT_COUNTER = 0; + + $document = ODD_Import($xml); + if (!$document) { + throw new ImportException(elgg_echo('ImportException:NoODDElements')); + } + + foreach ($document as $element) { + _process_element($element); + } + + if ($IMPORTED_OBJECT_COUNTER != count($IMPORTED_DATA)) { + throw new ImportException(elgg_echo('ImportException:NotAllImported')); + } + + return true; +} + + +/** + * Register the OpenDD import action + * + * @return void + * @access private + */ +function export_init() { + global $CONFIG; + + elgg_register_action("import/opendd"); +} + +// Register a startup event +elgg_register_event_handler('init', 'system', 'export_init', 100); diff --git a/engine/lib/extender.php b/engine/lib/extender.php new file mode 100644 index 000000000..538f601e1 --- /dev/null +++ b/engine/lib/extender.php @@ -0,0 +1,241 @@ +<?php +/** + * Elgg Entity Extender. + * This file contains ways of extending an Elgg entity in custom ways. + * + * @package Elgg.Core + * @subpackage DataModel.Extender + */ + +/** + * Detect the value_type for a given value. + * Currently this is very crude. + * + * @todo Make better! + * + * @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 != "" && ($value_type == 'integer' || $value_type == 'text')) { + return $value_type; + } + + // This is crude + if (is_int($value)) { + return 'integer'; + } + // Catch floating point values which are not integer + if (is_numeric($value)) { + return 'text'; + } + + return 'text'; +} + +/** + * 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 $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) + $type = $element->getAttribute('type'); + $attr_name = $element->getAttribute('name'); + $attr_val = $element->getBody(); + + switch ($type) { + // Ignore volatile items + case 'volatile' : + break; + case 'annotation' : + $entity->annotate($attr_name, $attr_val); + break; + case 'metadata' : + $entity->setMetaData($attr_name, $attr_val, "", true); + break; + default : // Anything else assume attribute + $entity->set($attr_name, $attr_val); + } + + // Set time if appropriate + $attr_time = $element->getAttribute('published'); + if ($attr_time) { + $entity->set('time_updated', $attr_time); + } + + return true; +} + +/** + * 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. + * @access private + */ +function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) { + $element = $params['element']; + + $tmp = NULL; + + if ($element instanceof ODDMetaData) { + // Recall entity + $entity_uuid = $element->getAttribute('entity_uuid'); + $entity = get_entity_from_uuid($entity_uuid); + if (!$entity) { + throw new ImportException(elgg_echo('ImportException:GUIDNotFound', array($entity_uuid))); + } + + oddmetadata_to_elggextender($entity, $element); + + // Save + if (!$entity->save()) { + $attr_name = $element->getAttribute('name'); + $msg = elgg_echo('ImportException:ProblemUpdatingMeta', array($attr_name, $entity_uuid)); + throw new ImportException($msg); + } + + return true; + } +} + +/** + * 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 bool + */ +function can_edit_extender($extender_id, $type, $user_guid = 0) { + if (!elgg_is_logged_in()) { + return false; + } + + $user_guid = (int)$user_guid; + $user = get_entity($user_guid); + if (!$user) { + $user = elgg_get_logged_in_user_entity(); + } + + $functionname = "elgg_get_{$type}_from_id"; + if (is_callable($functionname)) { + $extender = call_user_func($functionname, $extender_id); + } else { + return false; + } + + if (!($extender instanceof ElggExtender)) { + return false; + } + /* @var ElggExtender $extender */ + + // If the owner is the specified user, great! They can edit. + if ($extender->getOwnerGUID() == $user->getGUID()) { + 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())) { + return true; + } + + // Trigger plugin hooks + $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 such as elgg_register_annotation_url_handler(). + * + * @param string $extender_type Extender type ('annotation', 'metadata') + * @param string $extender_name The name of the extender + * @param string $function_name The function to register + * + * @return bool + */ +function elgg_register_extender_url_handler($extender_type, $extender_name, $function_name) { + + global $CONFIG; + + if (!is_callable($function_name, true)) { + return false; + } + + if (!isset($CONFIG->extender_url_handler)) { + $CONFIG->extender_url_handler = array(); + } + if (!isset($CONFIG->extender_url_handler[$extender_type])) { + $CONFIG->extender_url_handler[$extender_type] = array(); + } + $CONFIG->extender_url_handler[$extender_type][$extender_name] = $function_name; + + return true; +} + +/** + * Get the URL of a given elgg extender. + * Used by get_annotation_url and get_metadata_url. + * + * @param ElggExtender $extender An extender object + * + * @return string + */ +function get_extender_url(ElggExtender $extender) { + global $CONFIG; + + $view = elgg_get_viewtype(); + + $guid = $extender->entity_guid; + $type = $extender->type; + + $url = ""; + + $function = ""; + if (isset($CONFIG->extender_url_handler[$type][$extender->name])) { + $function = $CONFIG->extender_url_handler[$type][$extender->name]; + } + + if (isset($CONFIG->extender_url_handler[$type]['all'])) { + $function = $CONFIG->extender_url_handler[$type]['all']; + } + + if (isset($CONFIG->extender_url_handler['all']['all'])) { + $function = $CONFIG->extender_url_handler['all']['all']; + } + + if (is_callable($function)) { + $url = call_user_func($function, $extender); + } + + if ($url == "") { + $nameid = $extender->id; + if ($type == 'volatile') { + $nameid = $extender->name; + } + $url = "export/$view/$guid/$type/$nameid/"; + } + + return elgg_normalize_url($url); +} + +/** Register the hook */ +elgg_register_plugin_hook_handler("import", "all", "import_extender_plugin_hook", 2); diff --git a/engine/lib/filestore.php b/engine/lib/filestore.php new file mode 100644 index 000000000..93a127257 --- /dev/null +++ b/engine/lib/filestore.php @@ -0,0 +1,522 @@ +<?php +/** + * Elgg filestore. + * This file contains classes, interfaces and functions for + * saving and retrieving data to various file stores. + * + * @package Elgg.Core + * @subpackage DataModel.FileStorage + */ + +/** + * Get the size of the specified 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) { + $handle = @opendir($dir); + while ($file = @readdir($handle)) { + if (eregi("^\.{1,2}$", $file)) { + continue; + } + if (is_dir($dir . $file)) { + $totalsize = get_dir_size($dir . $file . "/", $totalsize); + } else { + $totalsize += filesize($dir . $file); + } + } + @closedir($handle); + + return($totalsize); +} + +/** + * Get the contents of an uploaded file. + * (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) { + // If the file exists ... + if (isset($_FILES[$input_name]) && $_FILES[$input_name]['error'] == 0) { + return file_get_contents($_FILES[$input_name]['tmp_name']); + } + return false; +} + +/** + * Gets the jpeg contents of the resized version of an uploaded image + * (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 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, $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, 0, 0, 0, 0, $upscale); + } + + return false; +} + +/** + * Gets the jpeg contents of the resized version of an already uploaded image + * (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 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) { + + // Get the size information from the image + $imgsizearray = getimagesize($input_name); + if ($imgsizearray == FALSE) { + return FALSE; + } + + $width = $imgsizearray[0]; + $height = $imgsizearray[1]; + + $accepted_formats = array( + 'image/jpeg' => 'jpeg', + 'image/pjpeg' => 'jpeg', + 'image/png' => 'png', + 'image/x-png' => 'png', + 'image/gif' => 'gif' + ); + + // make sure the function is available + $load_function = "imagecreatefrom" . $accepted_formats[$imgsizearray['mime']]; + if (!is_callable($load_function)) { + 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) { + $crop = FALSE; + } + + // how large a section of the image has been selected + if ($crop) { + $selection_width = $x2 - $x1; + $selection_height = $y2 - $y1; + } else { + // everything selected if no crop parameters + $selection_width = $width; + $selection_height = $height; + } + + // determine cropping offsets + if ($square) { + // asking for a square image back + + // detect case where someone is passing crop parameters that are not for a square + if ($crop == TRUE && $selection_width != $selection_height) { + return FALSE; + } + + // size of the new square image + $new_width = $new_height = min($maxwidth, $maxheight); + + // find largest square that fits within the selected region + $selection_width = $selection_height = min($selection_width, $selection_height); + + // set offsets for crop + if ($crop) { + $widthoffset = $x1; + $heightoffset = $y1; + $width = $x2 - $x1; + $height = $width; + } else { + // place square region in the center + $widthoffset = floor(($width - $selection_width) / 2); + $heightoffset = floor(($height - $selection_height) / 2); + } + } else { + // non-square new image + $new_width = $maxwidth; + $new_height = $maxheight; + + // maintain aspect ratio of original image/crop + 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 * $selection_height / (float)$selection_width); + } + + // by default, use entire image + $widthoffset = 0; + $heightoffset = 0; + + if ($crop) { + $widthoffset = $x1; + $heightoffset = $y1; + } + } + + 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; + } + } + } + + $params = array( + 'newwidth' => $new_width, + 'newheight' => $new_height, + 'selectionwidth' => $selection_width, + 'selectionheight' => $selection_height, + 'xoffset' => $widthoffset, + 'yoffset' => $heightoffset, + ); + + return $params; +} + +/** + * 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; + if ($thumbnail) { + $delfile = new ElggFile(); + $delfile->owner_guid = $file->owner_guid; + $delfile->setFilename($thumbnail); + $delfile->delete(); + } + if ($smallthumb) { + $delfile = new ElggFile(); + $delfile->owner_guid = $file->owner_guid; + $delfile->setFilename($smallthumb); + $delfile->delete(); + } + if ($largethumb) { + $delfile = new ElggFile(); + $delfile->owner_guid = $file->owner_guid; + $delfile->setFilename($largethumb); + $delfile->delete(); + } + + return $file->delete(); + } + } + + return false; +} + +/** + * 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) { + switch($mimetype) { + + case "application/msword": + return "document"; + break; + case "application/pdf": + return "document"; + break; + } + + if (substr_count($mimetype, 'text/')) { + return "document"; + } + + if (substr_count($mimetype, 'audio/')) { + return "audio"; + } + + if (substr_count($mimetype, 'image/')) { + return "image"; + } + + if (substr_count($mimetype, 'video/')) { + return "video"; + } + + if (substr_count($mimetype, 'opendocument')) { + return "document"; + } + + return "general"; +} + +/** + * Delete a directory and all its contents + * + * @param str $directory Directory to delete + * + * @return bool + */ +function delete_directory($directory) { + // sanity check: must be a directory + if (!$handle = opendir($directory)) { + return FALSE; + } + + // loop through all files + while (($file = readdir($handle)) !== FALSE) { + if (in_array($file, array('.', '..'))) { + continue; + } + + $path = "$directory/$file"; + if (is_dir($path)) { + // recurse down through directory + if (!delete_directory($path)) { + return FALSE; + } + } else { + // delete file + unlink($path); + } + } + + // remove empty directory + closedir($handle); + return rmdir($directory); +} + +/** + * Removes all user files + * + * @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) { + global $CONFIG; + + $time_created = date('Y/m/d', (int)$user->time_created); + $file_path = "$CONFIG->dataroot$time_created/$user->guid"; + if (file_exists($file_path)) { + delete_directory($file_path); + } +} + + +/// Variable holding the default datastore +$DEFAULT_FILE_STORE = NULL; + +/** + * Return the default filestore. + * + * @return ElggFilestore + */ +function get_default_filestore() { + global $DEFAULT_FILE_STORE; + + return $DEFAULT_FILE_STORE; +} + +/** + * 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; + + $DEFAULT_FILE_STORE = $filestore; + + return true; +} + +/** + * Register entity type objects, subtype file as + * ElggFile. + * + * @return void + * @access private + */ +function filestore_run_once() { + // Register a class + add_subtype("object", "file", "ElggFile"); +} + +/** + * Initialise the file modules. + * Listens to system init and configures the default filestore + * + * @return void + * @access private + */ +function filestore_init() { + global $CONFIG; + + // Now register a default filestore + if (isset($CONFIG->dataroot)) { + set_default_filestore(new ElggDiskFilestore($CONFIG->dataroot)); + } + + // Now run this stuff, but only once + run_function_once("filestore_run_once"); +} + +/** + * Unit tests for files + * + * @param sting $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 new file mode 100644 index 000000000..5a38e1ea6 --- /dev/null +++ b/engine/lib/group.php @@ -0,0 +1,339 @@ +<?php +/** + * Elgg Groups. + * Groups contain other entities, or rather act as a placeholder for other entities to + * mark any given container as their container. + * + * @package Elgg.Core + * @subpackage DataModel.Group + */ + +/** + * Get the group entity. + * + * @param int $guid GUID for a group + * + * @return array|false + * @access private + */ +function get_group_entity_as_row($guid) { + global $CONFIG; + + $guid = (int)$guid; + + return get_data_row("SELECT * from {$CONFIG->dbprefix}groups_entity where guid=$guid"); +} + +/** + * Create or update the entities table for a given group. + * Call create_entity first. + * + * @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; + + $guid = (int)$guid; + $name = sanitise_string($name); + $description = sanitise_string($description); + + $row = get_entity_as_row($guid); + + if ($row) { + // Exists and you have access to it + $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 (elgg_trigger_event('update', $entity->type, $entity)) { + return $guid; + } else { + $entity->delete(); + } + } + } else { + // Update failed, attempt an insert. + $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 (elgg_trigger_event('create', $entity->type, $entity)) { + return $guid; + } else { + $entity->delete(); + } + } + } + } + + return false; +} + +/** + * Add an object to the given group. + * + * @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; + $object_guid = (int)$object_guid; + + $group = get_entity($group_guid); + $object = get_entity($object_guid); + + if ((!$group) || (!$object)) { + return false; + } + + if (!($group instanceof ElggGroup)) { + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($group_guid, 'ElggGroup')); + throw new InvalidClassException($msg); + } + + if (!($object instanceof ElggObject)) { + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($object_guid, 'ElggObject')); + throw new InvalidClassException($msg); + } + + $object->container_guid = $group_guid; + return $object->save(); +} + +/** + * Remove an object from the given group. + * + * @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; + $object_guid = (int)$object_guid; + + $group = get_entity($group_guid); + $object = get_entity($object_guid); + + if ((!$group) || (!$object)) { + return false; + } + + if (!($group instanceof ElggGroup)) { + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($group_guid, 'ElggGroup')); + throw new InvalidClassException($msg); + } + + if (!($object instanceof ElggObject)) { + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($object_guid, 'ElggObject')); + throw new InvalidClassException($msg); + } + + $object->container_guid = $object->owner_guid; + return $object->save(); +} + +/** + * 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) { + + // in 1.7 0 means "not set." rewrite to make sense. + if (!$site_guid) { + $site_guid = ELGG_ENTITIES_ANY_VALUE; + } + + return elgg_get_entities_from_relationship(array( + 'relationship' => 'member', + 'relationship_guid' => $group_guid, + 'inverse_relationship' => TRUE, + 'types' => 'user', + 'limit' => $limit, + 'offset' => $offset, + 'count' => $count, + 'site_guid' => $site_guid + )); +} + +/** + * 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 + * + * @return bool + */ +function is_group_member($group_guid, $user_guid) { + $object = check_entity_relationship($user_guid, 'member', $group_guid); + if ($object) { + return true; + } else { + return false; + } +} + +/** + * Join a user to a group. + * + * @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); + + if ($result) { + $params = array('group' => get_entity($group_guid), 'user' => get_entity($user_guid)); + elgg_trigger_event('join', 'group', $params); + } + + return $result; +} + +/** + * Remove a user from a group. + * + * @param int $group_guid The group. + * @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 + $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; +} + +/** + * Return all groups a user is a member of. + * + * @param int $user_guid GUID of user + * + * @return array|false + */ +function get_users_membership($user_guid) { + $options = array( + 'relationship' => 'member', + 'relationship_guid' => $user_guid, + 'inverse_relationship' => FALSE + ); + return elgg_get_entities_from_relationship($options); +} + +/** + * 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. + * + * @return bool If $forward is set to false. + */ +function group_gatekeeper($forward = true) { + + $page_owner_guid = elgg_get_page_owner_guid(); + if (!$page_owner_guid) { + return true; + } + $visibility = ElggGroupItemVisibility::factory($page_owner_guid); + + 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() : ''; + + 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; +} + +/** + * Adds a group tool option + * + * @see remove_group_tool_option(). + * + * @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)) { + $CONFIG->group_tool_options = array(); + } + + $group_tool_option = new stdClass; + + $group_tool_option->name = $name; + $group_tool_option->label = $label; + $group_tool_option->default_on = $default_on; + + $CONFIG->group_tool_options[] = $group_tool_option; +} + +/** + * Removes a group tool option based on name + * + * @see add_group_tool_option() + * + * @param string $name Name of the group tool option + * + * @return void + * @since 1.7.5 + */ +function remove_group_tool_option($name) { + global $CONFIG; + + if (!isset($CONFIG->group_tool_options)) { + return; + } + + foreach ($CONFIG->group_tool_options as $i => $option) { + if ($option->name == $name) { + unset($CONFIG->group_tool_options[$i]); + } + } +} diff --git a/engine/lib/input.php b/engine/lib/input.php new file mode 100644 index 000000000..6d1646e1a --- /dev/null +++ b/engine/lib/input.php @@ -0,0 +1,513 @@ +<?php +/** + * Parameter input functions. + * This file contains functions for getting input from get/post variables. + * + * @package Elgg.Core + * @subpackage Input + */ + +/** + * 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. + * + * @return mixed + */ +function get_input($variable, $default = NULL, $filter_result = TRUE) { + + global $CONFIG; + + $result = $default; + + elgg_push_context('input'); + + if (isset($CONFIG->input[$variable])) { + $result = $CONFIG->input[$variable]; + + if ($filter_result) { + $result = filter_tags($result); + } + } elseif (isset($_REQUEST[$variable])) { + if (is_array($_REQUEST[$variable])) { + $result = $_REQUEST[$variable]; + } else { + $result = trim($_REQUEST[$variable]); + } + + if ($filter_result) { + $result = filter_tags($result); + } + } + + elgg_pop_context(); + + return $result; +} + +/** + * Sets an input value that may later be retrieved by get_input + * + * 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 + * + * @return void + */ +function set_input($variable, $value) { + global $CONFIG; + if (!isset($CONFIG->input)) { + $CONFIG->input = array(); + } + + if (is_array($value)) { + array_walk_recursive($value, create_function('&$v, $k', '$v = trim($v);')); + $CONFIG->input[trim($variable)] = $value; + } else { + $CONFIG->input[trim($variable)] = trim($value); + } +} + +/** + * Filter tags from a given string based on registered hooks. + * + * @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 elgg_trigger_plugin_hook('validate', 'input', null, $var); +} + +/** + * Validates an email address. + * + * @param string $address Email address. + * + * @return bool + */ +function is_email_address($address) { + return filter_var($address, FILTER_VALIDATE_EMAIL) === $address; +} + +/** + * 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) { + + 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; + } +} + +/** + * 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]); +} + +/** + * 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 + * + * @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; + } + return $default; +} + +/** + * 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 array + * @since 1.8.0 + */ +function elgg_get_sticky_values($form_name, $filter_result = true) { + if (!isset($_SESSION['sticky_forms'][$form_name])) { + return array(); + } + + $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. + * + * /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 + * + * @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 = elgg_get_logged_in_user_entity()) { + exit; + } + + if (!$q = get_input('term', get_input('q'))) { + exit; + } + + $q = sanitise_string($q); + + // replace mysql vars with escaped strings + $q = str_replace(array('_', '%'), array('\_', '\%'), $q); + + $match_on = get_input('match_on', 'all'); + + 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 = sanitise_int(get_input('limit', 10)); + + // grab a list of entities and send them in json. + $results = array(); + 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.name LIKE '% $q%' OR ue.username LIKE '$q%') + LIMIT $limit + "; + + if ($entities = get_data($query)) { + foreach ($entities as $entity) { + $entity = get_entity($entity->guid); + 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, + '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 (!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.name LIKE '% $q%' OR ge.description LIKE '% $q%') + LIMIT $limit + "; + if ($entities = get_data($query)) { + foreach ($entities as $entity) { + $entity = get_entity($entity->guid); + 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), + 'guid' => $entity->guid, + 'label' => $output, + 'value' => $entity->guid, + 'icon' => $icon, + 'url' => $entity->getURL(), + ); + + $results[$entity->name . rand(1, 100)] = $result; + } + } + break; + + case 'friends': + $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.name LIKE '% $q%' OR ue.username LIKE '$q%') + LIMIT $limit + "; + + if ($entities = get_data($query)) { + foreach ($entities as $entity) { + $entity = get_entity($entity->guid); + 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, + 'guid' => $entity->guid, + 'label' => $output, + 'value' => $entity->username, + 'icon' => $icon, + 'url' => $entity->getURL(), + ); + $results[$entity->name . rand(1, 100)] = $result; + } + } + break; + + default: + header("HTTP/1.0 400 Bad Request", true); + echo "livesearch: unknown match_on of $match_type"; + exit; + break; + } + } + + ksort($results); + 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. + elgg_register_page_handler('livesearch', 'input_livesearch_page_handler'); + + if (ini_get_bool('magic_quotes_gpc')) { + + /** + * 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(); + foreach ($array as $key => $data) { + if ($key != stripslashes($key)) { + $array2[stripslashes($key)] = $data; + } else { + $array2[$key] = $data; + } + } + return $array2; + } else { + return $array; + } + } + + /** + * 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); + $value = array_map('stripslashes_deep', $value); + } else { + $value = stripslashes($value); + } + return $value; + } + + $_POST = stripslashes_arraykeys($_POST); + $_GET = stripslashes_arraykeys($_GET); + $_COOKIE = stripslashes_arraykeys($_COOKIE); + $_REQUEST = stripslashes_arraykeys($_REQUEST); + + $_POST = array_map('stripslashes_deep', $_POST); + $_GET = array_map('stripslashes_deep', $_GET); + $_COOKIE = array_map('stripslashes_deep', $_COOKIE); + $_REQUEST = array_map('stripslashes_deep', $_REQUEST); + if (!empty($_SERVER['REQUEST_URI'])) { + $_SERVER['REQUEST_URI'] = stripslashes($_SERVER['REQUEST_URI']); + } + if (!empty($_SERVER['QUERY_STRING'])) { + $_SERVER['QUERY_STRING'] = stripslashes($_SERVER['QUERY_STRING']); + } + if (!empty($_SERVER['HTTP_REFERER'])) { + $_SERVER['HTTP_REFERER'] = stripslashes($_SERVER['HTTP_REFERER']); + } + if (!empty($_SERVER['PATH_INFO'])) { + $_SERVER['PATH_INFO'] = stripslashes($_SERVER['PATH_INFO']); + } + if (!empty($_SERVER['PHP_SELF'])) { + $_SERVER['PHP_SELF'] = stripslashes($_SERVER['PHP_SELF']); + } + if (!empty($_SERVER['PATH_TRANSLATED'])) { + $_SERVER['PATH_TRANSLATED'] = stripslashes($_SERVER['PATH_TRANSLATED']); + } + } +} + +elgg_register_event_handler('init', 'system', 'input_init'); diff --git a/engine/lib/languages.php b/engine/lib/languages.php new file mode 100644 index 000000000..98006f7cd --- /dev/null +++ b/engine/lib/languages.php @@ -0,0 +1,350 @@ +<?php +/** + * Elgg language module + * Functions to manage language and translations. + * + * @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: + * + * $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 + */ +function add_translation($country_code, $language_array) { + global $CONFIG; + if (!isset($CONFIG->translations)) { + $CONFIG->translations = array(); + } + + $country_code = strtolower($country_code); + $country_code = trim($country_code); + if (is_array($language_array) && sizeof($language_array) > 0 && $country_code != "") { + if (!isset($CONFIG->translations[$country_code])) { + $CONFIG->translations[$country_code] = $language_array; + } else { + $CONFIG->translations[$country_code] = $language_array + $CONFIG->translations[$country_code]; + } + return true; + } + return false; +} + +/** + * Detect the current language being used by the current site or logged in user. + * + * @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) { + $language = 'en'; + } + + return $language; +} + +/** + * Gets the current language in use by the system or user. + * + * @return string The language code (eg "en") or false if not set + */ +function get_language() { + global $CONFIG; + + $user = elgg_get_logged_in_user_entity(); + $language = false; + + if (($user) && ($user->language)) { + $language = $user->language; + } + + if ((!$language) && (isset($CONFIG->language)) && ($CONFIG->language)) { + $language = $CONFIG->language; + } + + if ($language) { + return $language; + } + + return false; +} + +function _elgg_load_translations() { + global $CONFIG; + + 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.php"); + if ($data) { + add_translation($language, unserialize($data)); + } else { + $loaded = false; + } + } + + if ($loaded) { + $CONFIG->i18n_loaded_from_cache = true; + // this is here to force + $CONFIG->language_paths[dirname(dirname(dirname(__FILE__))) . "/languages/"] = true; + return; + } + } + + // 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 + * + * @return void + */ +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(); + } + $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"); + + // 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; + } + } + } + + 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. + * + * @todo Better on demand loading based on language_paths array + * + * @return bool + */ +function reload_all_translations() { + global $CONFIG; + + static $LANG_RELOAD_ALL_RUN; + if ($LANG_RELOAD_ALL_RUN) { + return null; + } + + 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 array + */ +function get_installed_translations() { + global $CONFIG; + + // Ensure that all possible translations are loaded + reload_all_translations(); + + $installed = array(); + + foreach ($CONFIG->translations as $k => $v) { + $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') . ")"; + } + } + } + + return $installed; +} + +/** + * 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; + + // Ensure that all possible translations are loaded + reload_all_translations(); + + $language = sanitise_string($language); + + $en = count($CONFIG->translations['en']); + + $missing = get_missing_language_keys($language); + if ($missing) { + $missing = count($missing); + } else { + $missing = 0; + } + + //$lang = count($CONFIG->translations[$language]); + $lang = $en - $missing; + + return round(($lang / $en) * 100, 2); +} + +/** + * 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; + + // Ensure that all possible translations are loaded + reload_all_translations(); + + $missing = array(); + + foreach ($CONFIG->translations['en'] as $k => $v) { + if ((!isset($CONFIG->translations[$language][$k])) + || ($CONFIG->translations[$language][$k] == $CONFIG->translations['en'][$k])) { + $missing[] = $k; + } + } + + if (count($missing)) { + return $missing; + } + + return false; +} + +/** + * Initialize the language library + * @access private + */ +function elgg_languages_init() { + $lang = get_current_language(); + elgg_register_simplecache_view("js/languages/$lang"); +} + +elgg_register_event_handler('init', 'system', 'elgg_languages_init'); diff --git a/engine/lib/location.php b/engine/lib/location.php new file mode 100644 index 000000000..5b889509b --- /dev/null +++ b/engine/lib/location.php @@ -0,0 +1,157 @@ +<?php +/** + * Elgg geo-location tagging library. + * + * @package Elgg.Core + * @subpackage Location + */ + +/** + * Encode a location into a latitude and longitude, caching the result. + * + * 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" + * @return string|false + */ +function elgg_geocode_location($location) { + global $CONFIG; + + if (is_array($location)) { + return false; + } + + $location = sanitise_string($location); + + // Look for cached version + $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); + } + + // Trigger geocode event if not cached + $return = false; + $return = elgg_trigger_plugin_hook('geocode', 'location', array('location' => $location), $return); + + // If returned, cache and return value + if (($return) && (is_array($return))) { + $lat = (float)$return['lat']; + $long = (float)$return['long']; + + // Put into cache at the end of the page since we don't really care that much + $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; +} + +/** + * Return entities within a given geographic area. + * + * 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 elgg_get_entities_from_location(array $options = array()) { + + global $CONFIG; + + if (!isset($options['latitude']) || !isset($options['longitude']) || + !isset($options['distance'])) { + return false; + } + + if (!is_array($options['distance'])) { + $lat_distance = (float)$options['distance']; + $long_distance = (float)$options['distance']; + } else { + $lat_distance = (float)$options['distance']['latitude']; + $long_distance = (float)$options['distance']['longitude']; + } + + $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; + + $where = 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); + + // 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); + + return elgg_get_entities_from_relationship($options); +} + +/** + * Returns a viewable list of entities from location + * + * @param array $options + * + * @see elgg_list_entities() + * @see elgg_get_entities_from_location() + * + * @return string The viewable list of entities + * @since 1.8.0 + */ +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); diff --git a/engine/lib/mb_wrapper.php b/engine/lib/mb_wrapper.php new file mode 100644 index 000000000..c2f5503e0 --- /dev/null +++ b/engine/lib/mb_wrapper.php @@ -0,0 +1,233 @@ +<?php + +// if mb functions are available, set internal encoding to UTF8 +if (is_callable('mb_internal_encoding')) { + mb_internal_encoding("UTF-8"); + ini_set("mbstring.internal_encoding", 'UTF-8'); +} + +/** + * Parses a string using mb_parse_str() if available. + * NOTE: This differs from parse_str() by returning the results + * instead of placing them in the local scope! + * + * @param str $str The string + * + * @return array + * @since 1.7.0 + */ +function elgg_parse_str($str) { + if (is_callable('mb_parse_str')) { + mb_parse_str($str, $results); + } else { + parse_str($str, $results); + } + + return $results; +} + + + +/** + * 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); +} + +/** + * 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); +} + +/** + * 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 new file mode 100644 index 000000000..f79fba4a9 --- /dev/null +++ b/engine/lib/memcache.php @@ -0,0 +1,37 @@ +<?php +/** + * Elgg memcache support. + * + * Requires php5-memcache to work. + * + * @package Elgg.Core + * @subpackage Cache.Memcache + */ + +/** + * Return true if memcache is available and configured. + * + * @return bool + */ +function is_memcache_available() { + global $CONFIG; + + static $memcache_available; + + if ((!isset($CONFIG->memcache)) || (!$CONFIG->memcache)) { + return false; + } + + // If we haven't set variable to something + if (($memcache_available !== true) && ($memcache_available !== false)) { + try { + $tmp = new ElggMemcache(); + // No exception thrown so we have memcache available + $memcache_available = true; + } catch (Exception $e) { + $memcache_available = false; + } + } + + return $memcache_available; +} diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php new file mode 100644 index 000000000..f76c20f24 --- /dev/null +++ b/engine/lib/metadata.php @@ -0,0 +1,962 @@ +<?php +/** + * Elgg metadata + * Functions to manage entity metadata. + * + * @package Elgg.Core + * @subpackage DataModel.Metadata + */ + +/** + * Convert a database row to a new ElggMetadata + * + * @param stdClass $row An object from the database + * + * @return stdClass|ElggMetadata + * @access private + */ +function row_to_elggmetadata($row) { + if (!($row instanceof stdClass)) { + return $row; + } + + return new ElggMetadata($row); +} + +/** + * 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. + * + * @return ElggMetadata|false FALSE if not found + */ +function elgg_get_metadata_from_id($id) { + return elgg_get_metastring_based_object_from_id($id, 'metadata'); +} + +/** + * Deletes metadata using its ID. + * + * @param int $id The metadata ID to delete. + * @return bool + */ +function elgg_delete_metadata_by_id($id) { + $metadata = elgg_get_metadata_from_id($id); + if (!$metadata) { + return false; + } + return $metadata->delete(); +} + +/** + * Create a new metadata object, or update an existing one. + * + * 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 = 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))); + $time = time(); + $owner_guid = (int)$owner_guid; + $allow_multiple = (boolean)$allow_multiple; + + if (!isset($value)) { + return FALSE; + } + + if ($owner_guid == 0) { + $owner_guid = elgg_get_logged_in_user_guid(); + } + + $access_id = (int)$access_id; + + $query = "SELECT * from {$CONFIG->dbprefix}metadata" + . " WHERE entity_guid = $entity_guid and name_id=" . add_metastring($name) . " limit 1"; + + $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 { + // Support boolean types + if (is_bool($value)) { + $value = (int) $value; + } + + // Add the metastrings + $value_id = add_metastring($value); + if (!$value_id) { + return false; + } + + $name_id = add_metastring($name); + if (!$name_id) { + return false; + } + + // If ok then add it + $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)) { + + elgg_get_metadata_cache()->save($entity_guid, $name, $value, $allow_multiple); + + return $id; + } else { + elgg_delete_metadata_by_id($id); + } + } + } + + return $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 = elgg_get_metadata_from_id($id)) { + return false; + } + if (!$md->canEdit()) { + return false; + } + + // If memcached then we invalidate the cache for this entry + static $metabyname_memcache; + if ((!$metabyname_memcache) && (is_memcache_available())) { + $metabyname_memcache = new ElggMemcache('metabyname_memcache'); + } + + if ($metabyname_memcache) { + // @todo fix memcache (name_id is not a property of ElggMetadata) + $metabyname_memcache->delete("{$md->entity_guid}:{$md->name_id}"); + } + + $value_type = detect_extender_valuetype($value, sanitise_string(trim($value_type))); + + $owner_guid = (int)$owner_guid; + if ($owner_guid == 0) { + $owner_guid = elgg_get_logged_in_user_guid(); + } + + $access_id = (int)$access_id; + + // Support boolean types (as integers) + if (is_bool($value)) { + $value = (int) $value; + } + + // Add the metastring + $value = add_metastring($value); + if (!$value) { + return false; + } + + $name = add_metastring($name); + if (!$name) { + return false; + } + + // If ok then add it + $query = "UPDATE {$CONFIG->dbprefix}metadata" + . " set name_id='$name', value_id='$value', 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; +} + +/** + * This function creates metadata from an associative array of "key => value" pairs. + * + * 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) { + + foreach ($name_and_values as $k => $v) { + $result = create_metadata($entity_guid, $k, $v, $value_type, $owner_guid, + $access_id, $allow_multiple); + if (!$result) { + return false; + } + } + return true; +} + +/** + * Returns metadata. Accepts all elgg_get_entities() options for entity + * restraints. + * + * @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 mixed + * @since 1.8.0 + */ +function elgg_get_metadata(array $options = array()) { + $options['metastring_type'] = 'metadata'; + return elgg_get_metastring_based_objects($options); +} + +/** + * 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. + * + * @warning This returns null on no ops. + * + * @param array $options An options array. {@see elgg_get_metadata()} + * @return mixed Null if the metadata name is invalid. Bool on success or fail. + * @since 1.8.0 + */ +function elgg_delete_metadata(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) { + return false; + } + + elgg_get_metadata_cache()->invalidateByOptions('delete', $options); + + $options['metastring_type'] = 'metadata'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); +} + +/** + * Disables metadata based on $options. + * + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * + * @warning This returns null on no ops. + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return mixed + * @since 1.8.0 + */ +function elgg_disable_metadata(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) { + return false; + } + + elgg_get_metadata_cache()->invalidateByOptions('disable', $options); + + $options['metastring_type'] = 'metadata'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', false); +} + +/** + * Enables metadata based on $options. + * + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * + * @warning This returns null on no ops. + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return mixed + * @since 1.8.0 + */ +function elgg_enable_metadata(array $options) { + if (!$options || !is_array($options)) { + return false; + } + + elgg_get_metadata_cache()->invalidateByOptions('enable', $options); + + $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 + * ) + * Currently if multiple values are sent via + * an array (value => array('value1', 'value2') + * the pair's operand will be forced to "IN". + * + * 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 + * + * 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 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_name_value_pairs_operator' => 'AND', + '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', 'metadata_owner_guid'); + + $options = elgg_normalise_plural_options_array($options, $singulars); + + if (!$options = elgg_entities_get_metastrings_options('metadata', $options)) { + return FALSE; + } + + return elgg_get_entities($options); +} + +/** + * Returns metadata name and value SQL where for entities. + * NB: $names and $values are not paired. Use $pairs for this. + * Pairs default to '=' operand. + * + * 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($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) + && (!$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('n_table'); + + $return = array ( + 'joins' => 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) { + 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 {$CONFIG->dbprefix}metastrings msn on n_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 {$CONFIG->dbprefix}metastrings msv on n_table.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)) { + // 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. + + 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; + } + + // 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 = 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. + 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($pair['name']); + + // @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)) { + $wheres[] = "($where)"; + } + } + + // 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); + } + + $wheres[] = "(n_table.owner_guid IN ($owner_str))"; + } + + if ($where = implode(' AND ', $wheres)) { + $return['wheres'][] = "($where)"; + } + + 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++; + } + } + } + + return $return; +} + +/** + * Returns a list of entities filtered by provided metadata. + * + * @see elgg_get_entities_from_metadata + * + * @param array $options Options array + * + * @return array + * @since 1.7.0 + */ +function elgg_list_entities_from_metadata($options) { + return elgg_list_entities($options, 'elgg_get_entities_from_metadata'); +} + +/** + * Other functions + */ + +/** + * Handler called by trigger_plugin_hook on the "export" event. + * + * @param string $hook export + * @param string $entity_type all + * @param mixed $returnvalue Value returned from previous hook + * @param mixed $params Params + * + * @return array + * @access private + * + * @throws InvalidParameterException + */ +function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params) { + // Sanity check values + if ((!is_array($params)) && (!isset($params['guid']))) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); + } + + if (!is_array($returnvalue)) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonArrayReturnValue')); + } + + $result = elgg_get_metadata(array( + 'guid' => (int)$params['guid'], + 'limit' => 0, + )); + + if ($result) { + /* @var ElggMetadata[] $result */ + foreach ($result as $r) { + $returnvalue[] = $r->export(); + } + } + + return $returnvalue; +} + +/** + * 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); + $ar = array_filter($ar, 'is_not_null'); + return $ar; + } + return false; + +} + +/** + * 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) { + $valuearray[] = $element->value; + } + } + + return $valuearray; +} + +/** + * Get the URL for this metadata + * + * By default this links to the export handler in the current view. + * + * @param int $id Metadata ID + * + * @return mixed + */ +function get_metadata_url($id) { + $id = (int)$id; + + if ($extender = elgg_get_metadata_from_id($id)) { + return get_extender_url($extender); + } + return false; +} + +/** + * 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 $subtype The subtype; all subtypes by default + * + * @return void + */ +function register_metadata_as_independent($type, $subtype = '*') { + global $CONFIG; + if (!isset($CONFIG->independents)) { + $CONFIG->independents = array(); + } + $CONFIG->independents[$type][$subtype] = true; +} + +/** + * 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 $subtype The entity subtype + * + * @return bool + */ +function is_metadata_independent($type, $subtype) { + global $CONFIG; + if (empty($CONFIG->independents)) { + return false; + } + if (!empty($CONFIG->independents[$type][$subtype]) + || !empty($CONFIG->independents[$type]['*'])) { + return true; + } + return false; +} + +/** + * 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 + * + * @return true + */ +function metadata_update($event, $object_type, $object) { + if ($object instanceof ElggEntity) { + if (!is_metadata_independent($object->getType(), $object->getSubtype())) { + $db_prefix = elgg_get_config('dbprefix'); + $access_id = (int) $object->access_id; + $guid = (int) $object->getGUID(); + $query = "update {$db_prefix}metadata set access_id = {$access_id} where entity_guid = {$guid}"; + update_data($query); + } + } + return true; +} + +/** + * Register a metadata url handler. + * + * @param string $extender_name The name, default 'all'. + * @param string $function The function name. + * + * @return bool + */ +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 + */ +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 */ +elgg_register_plugin_hook_handler("export", "all", "export_metadata_plugin_hook", 2); + +/** Call a function whenever an entity is updated **/ +elgg_register_event_handler('update', 'all', 'metadata_update'); + +// unit testing +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/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 new file mode 100644 index 000000000..cf6dd4d98 --- /dev/null +++ b/engine/lib/metastrings.php @@ -0,0 +1,904 @@ +<?php +/** + * Elgg metastrngs + * Functions to manage object metastrings. + * + * @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 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) { + $result = array_search($string, $METASTRINGS_CACHE, true); + + if ($result !== false) { + elgg_log("** Returning id for string:$string from cache."); + return $result; + } + + // See if we have previously looked for this and found nothing + if (in_array($string, $METASTRINGS_DEADNAME_CACHE, true)) { + return false; + } + + // Experimental memcache + $msfc = null; + static $metastrings_memcache; + if ((!$metastrings_memcache) && (is_memcache_available())) { + $metastrings_memcache = new ElggMemcache('metastrings_memcache'); + } + if ($metastrings_memcache) { + $msfc = $metastrings_memcache->load($string); + } + if ($msfc) { + return $msfc; + } + } + + // Case sensitive + if ($case_sensitive) { + $query = "SELECT * from {$CONFIG->dbprefix}metastrings where string= BINARY '$string' limit 1"; + } else { + $query = "SELECT * from {$CONFIG->dbprefix}metastrings where string = '$string'"; + } + + $row = FALSE; + $metaStrings = get_data($query, "entity_row_to_elggstar"); + if (is_array($metaStrings)) { + if (sizeof($metaStrings) > 1) { + $ids = array(); + foreach ($metaStrings as $metaString) { + $ids[] = $metaString->id; + } + return $ids; + } else if (isset($metaStrings[0])) { + $row = $metaStrings[0]; + } + } + + if ($row) { + $METASTRINGS_CACHE[$row->id] = $row->string; // Cache it + + // Attempt to memcache it if memcache is available + if ($metastrings_memcache) { + $metastrings_memcache->save($row->string, $row->id); + } + + elgg_log("** Cacheing string '{$row->string}'"); + + return $row->id; + } else { + $METASTRINGS_DEADNAME_CACHE[$string] = $string; + } + + return false; +} + +/** + * When given an ID, returns the corresponding metastring + * + * @param int $id Metastring ID + * + * @return string Metastring + */ +function get_metastring($id) { + global $CONFIG, $METASTRINGS_CACHE; + + $id = (int) $id; + + if (isset($METASTRINGS_CACHE[$id])) { + elgg_log("** Returning string for id:$id from cache."); + + return $METASTRINGS_CACHE[$id]; + } + + $row = get_data_row("SELECT * from {$CONFIG->dbprefix}metastrings where id='$id' limit 1"); + if ($row) { + $METASTRINGS_CACHE[$id] = $row->string; // Cache it + elgg_log("** Cacheing string '{$row->string}'"); + + return $row->string; + } + + return false; +} + +/** + * 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? + * + * @return mixed Integer tag or false. + */ +function add_metastring($string, $case_sensitive = true) { + global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE; + + $sanstring = sanitise_string($string); + + $id = get_metastring_id($string, $case_sensitive); + if ($id) { + return $id; + } + + $result = insert_data("INSERT into {$CONFIG->dbprefix}metastrings (string) values ('$sanstring')"); + if ($result) { + $METASTRINGS_CACHE[$result] = $string; + if (isset($METASTRINGS_DEADNAME_CACHE[$string])) { + unset($METASTRINGS_DEADNAME_CACHE[$string]); + } + } + + return $result; +} + +/** + * Delete any orphaned entries in metastrings. This is run by the garbage collector. + * + * @return bool + * @access private + */ +function delete_orphaned_metastrings() { + global $CONFIG; + + // If memcache is enabled then we need to flush it of deleted values + if (is_memcache_available()) { + $select_query = " + SELECT * + from {$CONFIG->dbprefix}metastrings where + ( + (id not in (select name_id from {$CONFIG->dbprefix}metadata)) AND + (id not in (select value_id from {$CONFIG->dbprefix}metadata)) AND + (id not in (select name_id from {$CONFIG->dbprefix}annotations)) AND + (id not in (select value_id from {$CONFIG->dbprefix}annotations)) + )"; + + $dead = get_data($select_query); + if ($dead) { + static $metastrings_memcache; + if (!$metastrings_memcache) { + $metastrings_memcache = new ElggMemcache('metastrings_memcache'); + } + + foreach ($dead as $d) { + $metastrings_memcache->delete($d->string); + } + } + } + + $query = " + DELETE + from {$CONFIG->dbprefix}metastrings where + ( + (id not in (select name_id from {$CONFIG->dbprefix}metadata)) AND + (id not in (select value_id from {$CONFIG->dbprefix}metadata)) AND + (id not in (select name_id from {$CONFIG->dbprefix}annotations)) AND + (id not in (select value_id from {$CONFIG->dbprefix}annotations)) + )"; + + return delete_data($query); +} + +/** + * 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']; + + // allow count shortcut + if ($options['count']) { + $options['metastring_calculation'] = 'count'; + } + + // 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']); + } + + if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE) { + $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"; + } 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) { + 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 ''; + } + + $db_prefix = elgg_get_config('dbprefix'); + + // 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($table); + + $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 AND $access)"; + } elseif ($names_where) { + $wheres[] = "($names_where AND $access)"; + } elseif ($values_where) { + $wheres[] = "($values_where AND $access)"; + } + + 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 anntations + * + * @return bool + * @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 mixed + * + * @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) { + $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..86624cd7c --- /dev/null +++ b/engine/lib/navigation.php @@ -0,0 +1,516 @@ +<?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) { + 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 $index => $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' => $title, '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_name => $section) { + foreach ($section as $key => $item) { + if ($item->getSelected()) { + $selected = true; + break 2; + } + } + } + + if (!$selected) { + // nothing selected, match name to context + 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; + } + } + } + } + } + + 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']; + $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']; + $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']; + $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']; + + 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 new file mode 100644 index 000000000..9e3c075a8 --- /dev/null +++ b/engine/lib/notification.php @@ -0,0 +1,526 @@ +<?php +/** + * 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". + * + * 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. + * + * Catch NotificationException to trap errors. + * + * @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 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, true)) { + $NOTIFICATION_HANDLERS[$method] = new stdClass; + + $NOTIFICATION_HANDLERS[$method]->handler = $handler; + if ($params) { + foreach ($params as $k => $v) { + $NOTIFICATION_HANDLERS[$method]->$k = $v; + } + } + + return true; + } + + return false; +} + +/** + * 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. + * + * @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; + + // Sanitise + if (!is_array($to)) { + $to = array((int)$to); + } + $from = (int)$from; + //$subject = sanitise_string($subject); + + // Get notification methods + if (($methods_override) && (!is_array($methods_override))) { + $methods_override = array($methods_override); + } + + $result = array(); + + foreach ($to as $guid) { + // Results for a user are... + $result[$guid] = array(); + + if ($guid) { // Is the guid > 0? + // Are we overriding delivery? + $methods = $methods_override; + if (!$methods) { + $tmp = (array)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; + } + } + } + + 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) || (!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] = call_user_func($handler, + $from ? get_entity($from) : NULL, // From entity + get_entity($guid), // To entity + $subject, // The subject + $message, // Message + $params // Params + ); + } catch (Exception $e) { + error_log($e->getMessage()); + } + + } + } + } + } + + return $result; +} + +/** + * Get the notification settings for a given user. + * + * @param int $user_guid The user id + * + * @return stdClass + */ +function get_user_notification_settings($user_guid = 0) { + $user_guid = (int)$user_guid; + + if ($user_guid == 0) { + $user_guid = elgg_get_logged_in_user_guid(); + } + + // @todo: holy crap, really? + $all_metadata = elgg_get_metadata(array( + 'guid' => $user_guid, + 'limit' => 0 + )); + if ($all_metadata) { + $prefix = "notification:method:"; + $return = new stdClass; + + foreach ($all_metadata as $meta) { + $name = substr($meta->name, strlen($prefix)); + $value = $meta->value; + + if (strpos($meta->name, $prefix) === 0) { + $return->$name = $value; + } + } + + return $return; + } + + return false; +} + +/** + * 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). + * + * @return bool + */ +function set_user_notification_setting($user_guid, $method, $value) { + $user_guid = (int)$user_guid; + $method = sanitise_string($method); + + $user = get_entity($user_guid); + if (!$user) { + $user = elgg_get_logged_in_user_entity(); + } + + if (($user) && ($user instanceof ElggUser)) { + $prefix = "notification:method:$method"; + $user->$prefix = $value; + $user->save(); + + return true; + } + + return false; +} + +/** + * 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) + * + * @return bool + * @access private + */ +function email_notify_handler(ElggEntity $from, ElggUser $to, $subject, $message, +array $params = NULL) { + + global $CONFIG; + + if (!$from) { + $msg = elgg_echo('NotificationException:MissingParameter', array('from')); + throw new NotificationException($msg); + } + + if (!$to) { + $msg = elgg_echo('NotificationException:MissingParameter', array('to')); + throw new NotificationException($msg); + } + + if ($to->email == "") { + $msg = elgg_echo('NotificationException:NoEmailAddress', array($to->guid)); + throw new NotificationException($msg); + } + + // To + $to = $to->email; + + // From + $site = get_entity($CONFIG->site_guid); + // If there's an email address, use it - but only if its not from a user. + if (!($from instanceof ElggUser) && $from->email) { + $from = $from->email; + } else if ($site && $site->email) { + // Use email address of current site if we cannot use sender's email + $from = $site->email; + } else { + // If all else fails, use the domain of the site. + $from = 'noreply@' . get_site_domain($CONFIG->site_guid); + } + + 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 + * @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); + } + + 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) { + // Allow non-RFC 2822 mail headers to support some broken MTAs + $header_eol = "\n"; + } + + // 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{$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); + if (is_callable('mb_encode_mimeheader')) { + $subject = mb_encode_mimeheader($subject, "UTF-8", "B"); + } + + // Format message + $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($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 + elgg_extend_view('forms/account/settings', 'core/settings/account/notifications'); + + 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 $object_subtype Its subtype + * @param string $language_name Its localized notification string (eg "New blog post") + * + * @return void + */ +function register_notification_object($entity_type, $object_subtype, $language_name) { + global $CONFIG; + + if ($entity_type == '') { + $entity_type = '__BLANK__'; + } + if ($object_subtype == '') { + $object_subtype = '__BLANK__'; + } + + if (!isset($CONFIG->register_objects)) { + $CONFIG->register_objects = array(); + } + + if (!isset($CONFIG->register_objects[$entity_type])) { + $CONFIG->register_objects[$entity_type] = array(); + } + + $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 $author_guid The GUID of the user whose content the user wants to follow + * + * @return true|false Depending on success + */ +function register_notification_interest($user_guid, $author_guid) { + return add_entity_relationship($user_guid, 'notify', $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 $author_guid The GUID of the user whose content the user wants to unfollow + * + * @return true|false Depending on success + */ +function remove_notification_interest($user_guid, $author_guid) { + return remove_entity_relationship($user_guid, 'notify', $author_guid); +} + +/** + * Automatically triggered notification on 'create' events that looks at registered + * 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 void + * @access private + */ +function object_notifications($event, $object_type, $object) { + // We only want to trigger notification events for ElggEntities + if ($object instanceof ElggEntity) { + + // Get config data + global $CONFIG, $SESSION, $NOTIFICATION_HANDLERS; + + $hookresult = elgg_trigger_plugin_hook('object:notifications', $object_type, array( + 'event' => $event, + 'object_type' => $object_type, + 'object' => $object, + ), false); + if ($hookresult === true) { + return true; + } + + // Have we registered notifications for this type of entity? + $object_type = $object->getType(); + if (empty($object_type)) { + $object_type = '__BLANK__'; + } + + $object_subtype = $object->getSubtype(); + if (empty($object_subtype)) { + $object_subtype = '__BLANK__'; + } + + if (isset($CONFIG->register_objects[$object_type][$object_subtype])) { + $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) { + $interested_users = elgg_get_entities_from_relationship(array( + 'site_guids' => ELGG_ENTITIES_ANY_VALUE, + 'relationship' => 'notify' . $method, + 'relationship_guid' => $object->container_guid, + 'inverse_relationship' => TRUE, + 'types' => 'user', + 'limit' => 99999 + )); + + if ($interested_users && is_array($interested_users)) { + foreach ($interested_users as $user) { + if ($user instanceof ElggUser && !$user->isBanned()) { + if (($user->guid != $SESSION['user']->guid) && has_access_to_entity($object, $user) + && $object->access_id != ACCESS_PRIVATE) { + $body = elgg_trigger_plugin_hook('notify:entity:message', $object->getType(), array( + 'entity' => $object, + 'to_entity' => $user, + 'method' => $method), $string); + if (empty($body) && $body !== false) { + $body = $string; + } + if ($body !== false) { + notify_user($user->guid, $object->container_guid, $subject, $body, + null, array($method)); + } + } + } + } + } + } + } + } +} + +// Register a startup event +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 new file mode 100644 index 000000000..e5e8f67c4 --- /dev/null +++ b/engine/lib/objects.php @@ -0,0 +1,120 @@ +<?php +/** + * Elgg objects + * Functions to manage multiple or single objects in an Elgg install + * + * @package Elgg + * @subpackage Core + */ + +/** + * Return the object specific details of a object by a row. + * + * @param int $guid The guid to retreive + * + * @return bool + * @access private + */ +function get_object_entity_as_row($guid) { + global $CONFIG; + + $guid = (int)$guid; + return get_data_row("SELECT * from {$CONFIG->dbprefix}objects_entity where guid=$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 string $description The object's description + * + * @return bool + * @access private + */ +function create_object_entity($guid, $title, $description) { + global $CONFIG; + + $guid = (int)$guid; + $title = sanitise_string($title); + $description = sanitise_string($description); + + $row = get_entity_as_row($guid); + + if ($row) { + // Core entities row exists and we have access to it + $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); + elgg_trigger_event('update', $entity->type, $entity); + return $guid; + } + } else { + // Update failed, attempt an insert. + $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 (elgg_trigger_event('create', $entity->type, $entity)) { + return $guid; + } else { + $entity->delete(); + } + } + } + } + + 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 + * + * @return false|array On success, an array of ElggSites + */ +function get_object_sites($object_guid, $limit = 10, $offset = 0) { + $object_guid = (int)$object_guid; + $limit = (int)$limit; + $offset = (int)$offset; + + return elgg_get_entities_from_relationship(array( + 'relationship' => 'member_of_site', + 'relationship_guid' => $object_guid, + 'types' => 'site', + 'limit' => $limit, + 'offset' => $offset + )); +} + +/** + * Runs unit tests for ElggObject + * + * @param sting $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; + $value[] = "{$CONFIG->path}engine/tests/objects/objects.php"; + return $value; +} + +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 new file mode 100644 index 000000000..f00ea6aab --- /dev/null +++ b/engine/lib/opendd.php @@ -0,0 +1,105 @@ +<?php +/** + * OpenDD PHP Library. + * + * @package Elgg.Core + * @subpackage ODD + * @version 0.4 + */ + +/** + * 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) { + $name = $element->name; + $odd = false; + + switch ($name) { + case 'entity' : + $odd = new ODDEntity("", "", ""); + break; + case 'metadata' : + $odd = new ODDMetaData("", "", "", ""); + break; + case 'relationship' : + $odd = new ODDRelationship("", "", ""); + break; + } + + // Now populate values + if ($odd) { + // Attributes + foreach ($element->attributes as $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)); + } + + $odd->setBody($body); + } + + return $odd; +} + +/** + * Import an ODD document. + * + * @param string $xml The XML ODD. + * + * @return ODDDocument + * @access private + */ +function ODD_Import($xml) { + // Parse XML to an array + $elements = xml_to_object($xml); + + // Sanity check 1, was this actually XML? + if ((!$elements) || (!$elements->children)) { + return false; + } + + // Create ODDDocument + $document = new ODDDocument(); + + // Itterate through array of elements and construct ODD document + $cnt = 0; + + foreach ($elements->children as $child) { + $odd = ODD_factory($child); + + if ($odd) { + $document->addElement($odd); + $cnt++; + } + } + + // Check that we actually found something + if ($cnt == 0) { + return false; + } + + return $document; +} + +/** + * Export an ODD Document. + * + * @param ODDDocument $document The Document. + * + * @return string + * @access private + */ +function ODD_Export(ODDDocument $document) { + return "$document"; +} diff --git a/engine/lib/output.php b/engine/lib/output.php new file mode 100644 index 000000000..9295f2173 --- /dev/null +++ b/engine/lib/output.php @@ -0,0 +1,448 @@ +<?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 stirng with formatted links + **/ +function parse_urls($text) { + // @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', + // + // 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\">$urltext</a>$period"; + ' + ), $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', '<'); + + $validated = false; + 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; + } + + // handle some special cases + $title = str_replace('&', 'and', $title); + // quotes and angle brackets stored in the database as html encoded + $title = htmlspecialchars_decode($title); + + $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; +} + +/** + * Unit tests for Output + * + * @param sting $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 new file mode 100644 index 000000000..0cf99b6fe --- /dev/null +++ b/engine/lib/pagehandler.php @@ -0,0 +1,150 @@ +<?php +/** + * Elgg page handler functions + * + * @package Elgg.Core + * @subpackage Routing + */ + +/** + * 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 bool + * @access private + */ +function page_handler($handler, $page) { + global $CONFIG; + + elgg_set_context($handler); + + $page = explode('/', $page); + // remove empty array element when page url ends in a / (see #1480) + if ($page[count($page) - 1] === '') { + array_pop($page); + } + + // 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; + } + + $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 || 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' + * 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); + * + * 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'. + * + * 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 bool Depending on success + */ +function elgg_register_page_handler($handler, $function) { + global $CONFIG; + + if (!isset($CONFIG->pagehandler)) { + $CONFIG->pagehandler = array(); + } + if (is_callable($function, true)) { + $CONFIG->pagehandler[$handler] = $function; + return true; + } + + return false; +} + +/** + * Unregister a page handler for an identifier + * + * Note: to replace a page handler, call elgg_register_page_handler() + * + * @param string $handler The page type identifier + * + * @since 1.7.2 + * @return void + */ +function elgg_unregister_page_handler($handler) { + global $CONFIG; + + if (!isset($CONFIG->pagehandler)) { + return; + } + + 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 { + $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'); +} + +elgg_register_event_handler('init', 'system', 'page_handler_init'); diff --git a/engine/lib/pageowner.php b/engine/lib/pageowner.php new file mode 100644 index 000000000..94765feee --- /dev/null +++ b/engine/lib/pageowner.php @@ -0,0 +1,294 @@ +<?php +/** + * Elgg page owner library + * Contains functions for managing page ownership and context + * + * @package Elgg.Core + * @subpackage PageOwner + */ + +/** + * 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; + + 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); + + $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 ElggEntity|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); +} + +/** + * 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) { + + if ($returnvalue) { + return $returnvalue; + } + + $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)) { + elgg_set_ignore_access($ia); + return $entity->getGUID(); + } + } + + if ($user = get_user_by_username($username)) { + return $user->getGUID(); + } + } + + $owner = get_input("owner_guid"); + if ($owner) { + if ($user = get_entity($owner)) { + elgg_set_ignore_access($ia); + return $user->getGUID(); + } + } + + // 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, "?")); + } + + // @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; + } + } + } + + elgg_set_ignore_access($ia); +} + +/** + * 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 elgg_set_context($context) { + global $CONFIG; + + $context = trim($context); + + if (empty($context)) { + return false; + } + + $context = strtolower($context); + + array_pop($CONFIG->context); + array_push($CONFIG->context, $context); + + return true; +} + +/** + * Get the current context. + * + * Since context is a stack, this is equivalent to a peek. + * + * @return string|NULL + * @since 1.8.0 + */ +function elgg_get_context() { + global $CONFIG; + + if (!$CONFIG->context) { + return null; + } + + return $CONFIG->context[count($CONFIG->context) - 1]; +} + +/** + * Push a context onto the top of the stack + * + * @param string $context The context string to add to the context stack + * @return void + * @since 1.8.0 + */ +function elgg_push_context($context) { + global $CONFIG; + + array_push($CONFIG->context, $context); +} + +/** + * 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); +} + +/** + * Check if this context exists anywhere in the stack + * + * 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 elgg_in_context($context) { + global $CONFIG; + + return in_array($context, $CONFIG->context); +} + +/** + * Initializes the page owner functions + * + * @note This is on the 'boot, system' event so that the context is set up quickly. + * + * @return void + * @access private + */ +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); + } +} + +elgg_register_event_handler('boot', 'system', 'page_owner_boot'); diff --git a/engine/lib/pam.php b/engine/lib/pam.php new file mode 100644 index 000000000..1c9c3bfe1 --- /dev/null +++ b/engine/lib/pam.php @@ -0,0 +1,76 @@ +<?php +/** + * Elgg Simple PAM library + * Contains functions for managing authentication. + * 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 + * sessions.php. + * + * For more information on PAMs see: + * http://www.freebsd.org/doc/en/articles/pam/index.html + * + * @see ElggPAM + * + * @package Elgg.Core + * @subpackage Authentication.PAM + */ + +global $_PAM_HANDLERS; +$_PAM_HANDLERS = array(); + +/** + * Register a PAM handler. + * + * 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 bool + */ +function register_pam_handler($handler, $importance = "sufficient", $policy = "user") { + global $_PAM_HANDLERS; + + // setup array for this type of pam if not already set + if (!isset($_PAM_HANDLERS[$policy])) { + $_PAM_HANDLERS[$policy] = array(); + } + + // @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; + $_PAM_HANDLERS[$policy][$handler]->importance = strtolower($importance); + + return true; + } + + return false; +} + +/** + * Unregisters a PAM handler. + * + * @param string $handler The PAM handler function name + * @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]); +} diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php new file mode 100644 index 000000000..94aff277e --- /dev/null +++ b/engine/lib/plugins.php @@ -0,0 +1,1129 @@ +<?php +/** + * Elgg plugins library + * Contains functions for managing plugins + * + * @package Elgg.Core + * @subpackage Plugins + */ + +/** + * Tells ElggPlugin::start() to include the start.php file. + */ +define('ELGG_PLUGIN_INCLUDE_START', 1); + +/** + * Tells ElggPlugin::start() to automatically register the plugin's views. + */ +define('ELGG_PLUGIN_REGISTER_VIEWS', 2); + +/** + * 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 + * + * @todo Can't namespace these because many plugins directly call + * private settings via $entity->$name. + */ +//define('ELGG_PLUGIN_SETTING_PREFIX', 'plugin:setting:'); + +/** + * Prefix for plugin user setting names + */ +define('ELGG_PLUGIN_USER_SETTING_PREFIX', 'plugin:user_setting:'); + +/** + * Internal settings prefix + * + * @todo This could be resolved by promoting ElggPlugin to a 5th type. + */ +define('ELGG_PLUGIN_INTERNAL_PREFIX', 'elgg:internal:'); + + +/** + * 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(); + } + + $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; +} + +/** + * 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() { + $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); + + if (!$known_plugins) { + $known_plugins = array(); + } + + // 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); + + if (!$physical_plugins) { + 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->enabled != 'yes') { + $plugin->enable(); + $plugin->deactivate(); + $plugin->setPriority('last'); + } + + // remove from the list of plugins to disable + unset($known_plugins[$index]); + } else { + // add new plugins + // priority is force to last in save() if not set. + $plugin = new ElggPlugin($plugin_id); + $plugin->save(); + } + } + + // 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; +} + +/** + * Cache a reference to this plugin by its ID + * + * @param ElggPlugin $plugin + * + * @access private + */ +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); +} + +/** + * Returns an ElggPlugin object with the path $path. + * + * @param string $plugin_id The id (dir name) of the plugin. NOT the guid. + * @return mixed ElggPlugin or 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]; + } + + $plugin_id = sanitize_string($plugin_id); + $db_prefix = get_config('dbprefix'); + + $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 + ); + + $plugins = elgg_get_entities($options); + + if ($plugins) { + return $plugins[0]; + } + + return false; +} + +/** + * Returns if a plugin exists in the system. + * + * @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. + * + * @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 + * + * @return int + * @since 1.8.0 + * @access private + */ +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; + } + + // can't have a priority of 0. + return ($max) ? $max : 1; +} + +/** + * 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 (!($site instanceof ElggSite)) { + return false; + } + + $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() { + global $CONFIG; + + $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; + } + + if (elgg_get_config('i18n_loaded_from_cache')) { + $start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_LANGUAGES; + } + + $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; + } + } + } + + return $return; +} + +/** + * 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 array + * @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'); + + 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; + } + + $old_ia = elgg_set_ignore_access(true); + $plugins = elgg_get_entities_from_relationship($options); + elgg_set_ignore_access($old_ia); + + return $plugins; +} + +/** + * 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. + * + * @param array $order An array of plugin ids in the order to set them + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_plugin_priorities(array $order) { + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + + $plugins = elgg_get_plugins('any'); + if (!$plugins) { + return false; + } + + $return = true; + + // reindex to get standard counting. no need to increment by 10. + // though we do start with 1 + $order = array_values($order); + + foreach ($plugins as $plugin) { + $plugin_id = $plugin->getID(); + + if (!in_array($plugin_id, $order)) { + $missing_plugins[] = $plugin; + continue; + } + + $priority = array_search($plugin_id, $order) + 1; + + if (!$plugin->set($name, $priority)) { + $return = false; + break; + } + } + + // 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; + } + } + } + + 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). + * + * i.e., if the last plugin was in /mod/foobar/, this 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. + * + * @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 elgg_get_calling_plugin_id($mainfilename = false) { + if (!$mainfilename) { + if ($backtrace = debug_backtrace()) { + 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)) { + return $matches[1]; + } + } + } + } else { + //@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)) { + return $matches[1]; + } + } + } + return false; +} + +/** + * Returns an array of all provides from all active plugins. + * + * Array in the form array( + * 'provide_type' => array( + * 'provided_name' => array( + * 'version' => '1.8', + * 'provided_by' => 'provider_plugin_id' + * ) + * ) + * ) + * + * @param string $type The type of provides to return + * @param string $name A specific provided name to return. Requires $provide_type. + * + * @return array + * @since 1.8.0 + * @access private + */ +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() + ); + } + } + } + } + + 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 $provides; +} + +/** + * 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 elgg_check_plugins_provides($type, $name, $version = null, $comparison = 'ge') { + if (!$provided = elgg_get_plugins_provides($type, $name)) { + return array( + 'status' => false, + 'version' => '' + ); + } + + if ($provided) { + if ($version) { + $status = version_compare($provided['version'], $version, $comparison); + } else { + $status = true; + } + } + + return array( + 'status' => $status, + 'value' => $provided['version'] + ); +} + +/** + * 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 array $dep An ElggPluginPackage dependency array + * @return array + * @since 1.8.0 + * @access private + */ +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; + } + + // 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 $strings; +} + +/** + * Returns the ElggPlugin entity of the last plugin called. + * + * @return mixed ElggPlugin or false + * @since 1.8.0 + * @access private + */ +function elgg_get_calling_plugin_entity() { + $plugin_id = elgg_get_calling_plugin_id(); + + if ($plugin_id) { + return elgg_get_plugin_from_id($plugin_id); + } + + 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(); + } + + if (!$plugin instanceof ElggPlugin) { + return false; + } + + $settings = $plugin->getAllUserSettings($user_guid); + + if ($settings && $return_obj) { + $return = new stdClass; + + foreach ($settings as $k => $v) { + $return->$k = $v; + } + + return $return; + } else { + return $settings; + } +} + +/** + * 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 + * @since 1.8.0 + */ +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(); + } + + if (!$plugin) { + return false; + } + + return $plugin->setUserSetting($name, $value, $user_guid); +} + +/** + * Unsets a user-specific plugin setting + * + * @param str $name Name of the setting + * @param int $user_guid Defaults to logged in user + * @param str $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(); + } + + if (!$plugin) { + return false; + } + + return $plugin->unsetUserSetting($name, $user_guid); +} + +/** + * Get a user specific setting for a plugin. + * + * @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 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 (!$plugin) { + return false; + } + + return $plugin->getUserSetting($name, $user_guid); +} + +/** + * Set a setting for a plugin. + * + * @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 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(); + } + + if (!$plugin) { + return false; + } + + return $plugin->setSetting($name, $value); +} + +/** + * Get setting for a plugin. + * + * @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 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 false; + } + + return $plugin->getSetting($name); +} + +/** + * 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. + * + * @return bool + * @since 1.8.0 + */ +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 false; + } + + return $plugin->unsetSetting($name); +} + +/** + * 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 + * @since 1.8.0 + */ +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(); + } + + if (!$plugin) { + return false; + } + + return $plugin->unsetAllSettings(); +} + +/** + * Returns entities based upon plugin settings. + * Takes all the options for {@see elgg_get_entities_from_private_settings()} + * in addition to the ones below. + * + * @param array $options Array in the format: + * + * plugin_id => NULL|STR The plugin id. Defaults to calling plugin + * + * 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 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(); + } + + $singulars = array('plugin_user_setting_name', 'plugin_user_setting_value', + 'plugin_user_setting_name_value_pair'); + + $options = elgg_normalise_plural_options_array($options, $singulars); + + // 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]; + } + } + + + $plugin_id = $options['plugin_id']; + $prefix = elgg_namespace_plugin_private_setting('user_setting', '', $plugin_id); + $options['private_setting_name_prefix'] = $prefix; + + return elgg_get_entities_from_private_settings($options); +} + +/** + * Register object, plugin entities as ElggPlugin classes + * + * @return void + * @access private + */ +function plugin_run_once() { + add_subtype("object", "plugin", "ElggPlugin"); +} + +/** + * Runs unit tests for the entity objects. + * + * @param sting $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params + * + * @return array + * @access private + */ +function plugins_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/plugins.php'; + return $value; +} + +/** + * Initialize the plugin system + * Listens to system init and registers actions + * + * @return void + * @access private + */ +function plugin_init() { + run_function_once("plugin_run_once"); + + elgg_register_plugin_hook_handler('unit_test', 'system', 'plugins_test'); + + 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'); + + elgg_register_action('admin/plugins/set_priority', '', 'admin'); + + elgg_register_library('elgg:markdown', elgg_get_root_path() . 'vendors/markdown/markdown.php'); +} + +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/relationships.php b/engine/lib/relationships.php new file mode 100644 index 000000000..01654b1ce --- /dev/null +++ b/engine/lib/relationships.php @@ -0,0 +1,645 @@ +<?php +/** + * Elgg relationships. + * Stub containing relationship functions, making import and export easier. + * + * @package Elgg.Core + * @subpackage DataModel.Relationship + */ + +/** + * Convert a database row to a new ElggRelationship + * + * @param stdClass $row Database row from the relationship table + * + * @return stdClass or ElggMetadata + * @access private + */ +function row_to_elggrelationship($row) { + if (!($row instanceof stdClass)) { + return $row; + } + + return new ElggRelationship($row); +} + +/** + * Return a relationship. + * + * @param int $id The ID of a relationship + * + * @return mixed + */ +function get_relationship($id) { + global $CONFIG; + + $id = (int)$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 The relationship ID + * + * @return bool + */ +function delete_relationship($id) { + global $CONFIG; + + $id = (int)$id; + + $relationship = get_relationship($id); + + if (elgg_trigger_event('delete', 'relationship', $relationship)) { + return delete_data("delete from {$CONFIG->dbprefix}entity_relationships where id=$id"); + } + + return FALSE; +} + +/** + * Define an arbitrary relationship between two entities. + * This relationship could be a friendship, a group membership or a site membership. + * + * This function lets you make the statement "$guid_one is a $relationship of $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; + + $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, time_created) + values ($guid_one, '$relationship', $guid_two, $time)"); + + if ($result !== false) { + $obj = get_relationship($result); + if (elgg_trigger_event('create', $relationship, $obj)) { + return true; + } else { + delete_relationship($result); + } + } + + return false; +} + +/** + * 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 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 + */ +function check_entity_relationship($guid_one, $relationship, $guid_two) { + global $CONFIG; + + $guid_one = (int)$guid_one; + $relationship = sanitise_string($relationship); + $guid_two = (int)$guid_two; + + $query = "SELECT * FROM {$CONFIG->dbprefix}entity_relationships + WHERE guid_one=$guid_one + AND relationship='$relationship' + AND guid_two=$guid_two limit 1"; + + $row = get_data_row($query); + if ($row) { + return $row; + } + + return false; +} + +/** + * Remove an arbitrary relationship between two entities. + * + * @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; + + $guid_one = (int)$guid_one; + $relationship = sanitise_string($relationship); + $guid_two = (int)$guid_two; + + $obj = check_entity_relationship($guid_one, $relationship, $guid_two); + if ($obj == false) { + return false; + } + + 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; + } +} + +/** + * 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 (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; + + $guid_one = (int) $guid_one; + + if (!empty($relationship)) { + $relationship = sanitise_string($relationship); + $where = "and er.relationship='$relationship'"; + } else { + $where = ""; + } + + if (!empty($type)) { + $type = sanitise_string($type); + if (!$inverse) { + $join = " join {$CONFIG->dbprefix}entities e on e.guid = er.guid_two "; + } else { + $join = " join {$CONFIG->dbprefix}entities e on e.guid = er.guid_one "; + $where .= " and "; + } + $where .= " and e.type = '{$type}' "; + } else { + $join = ""; + } + + if (!$inverse) { + $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}"; + + return delete_data($sql); + } +} + +/** + * Get all the relationships for a given guid. + * + * @param int $guid The GUID of the relationship owner + * @param bool $inverse_relationship Inverse relationship owners? + * + * @return mixed + */ +function get_entity_relationships($guid, $inverse_relationship = FALSE) { + global $CONFIG; + + $guid = (int)$guid; + + $where = ($inverse_relationship ? "guid_two='$guid'" : "guid_one='$guid'"); + + $query = "SELECT * from {$CONFIG->dbprefix}entity_relationships where {$where}"; + + 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: + * + * relationship => NULL|STR relationship + * + * relationship_guid => NULL|INT Guid of relationship to test + * + * inverse_relationship => BOOL Inverse the relationship + * + * @return mixed If count, int. If not count, array. false on errors. + * @since 1.7.0 + */ +function elgg_get_entities_from_relationship($options) { + $defaults = array( + 'relationship' => NULL, + 'relationship_guid' => NULL, + 'inverse_relationship' => FALSE + ); + + $options = array_merge($defaults, $options); + + $clauses = elgg_get_entity_relationship_where_sql('e.guid', $options['relationship'], + $options['relationship_guid'], $options['inverse_relationship']); + + 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 (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); +} + +/** + * Returns sql appropriate for relationship joins and wheres + * + * @todo add support for multiple relationships and guids. + * + * @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 string $inverse_relationship Inverse relationship check? + * + * @return mixed + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_relationship_where_sql($column, $relationship = NULL, +$relationship_guid = NULL, $inverse_relationship = FALSE) { + + if ($relationship == NULL && $relationship_guid == NULL) { + return ''; + } + + global $CONFIG; + + $wheres = array(); + $joins = array(); + + if ($inverse_relationship) { + $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_one = $column"; + } else { + $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_two = $column"; + } + + if ($relationship) { + $wheres[] = "r.relationship = '" . sanitise_string($relationship) . "'"; + } + + if ($relationship_guid) { + if ($inverse_relationship) { + $wheres[] = "r.guid_two = '$relationship_guid'"; + } else { + $wheres[] = "r.guid_one = '$relationship_guid'"; + } + } + + if ($where_str = implode(' AND ', $wheres)) { + + return array('wheres' => array("($where_str)"), 'joins' => $joins); + } + + return ''; +} + +/** + * Returns a viewable list of entities by relationship + * + * @param array $options + * + * @see elgg_list_entities() + * @see elgg_get_entities_from_relationship() + * + * @return string The viewable list of entities + */ +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. + * + * @param array $options An options array compatible with + * elgg_get_entities_from_relationship() + * @return mixed int If count, int. If not count, array. false on errors. + * @since 1.8.0 + */ +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); +} + +/** + * Returns a list of entities by relationship count + * + * @see elgg_get_entities_from_relationship_count() + * + * @param array $options Options array + * + * @return array + * @since 1.8.0 + */ +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 $relationship_type The relationship type. + * @param string $function_name The function to register + * + * @return bool Depending on success + */ +function elgg_register_relationship_url_handler($relationship_type, $function_name) { + global $CONFIG; + + if (!is_callable($function_name, true)) { + return false; + } + + if (!isset($CONFIG->relationship_url_handler)) { + $CONFIG->relationship_url_handler = array(); + } + + $CONFIG->relationship_url_handler[$relationship_type] = $function_name; + + return true; +} + +/** + * Get the url for a given relationship. + * + * @param int $id Relationship ID + * + * @return string + */ +function get_relationship_url($id) { + global $CONFIG; + + $id = (int)$id; + + if ($relationship = get_relationship($id)) { + $view = elgg_get_viewtype(); + + $guid = $relationship->guid_one; + $type = $relationship->relationship; + + $url = ""; + + $function = ""; + if (isset($CONFIG->relationship_url_handler[$type])) { + $function = $CONFIG->relationship_url_handler[$type]; + } + if (isset($CONFIG->relationship_url_handler['all'])) { + $function = $CONFIG->relationship_url_handler['all']; + } + + if (is_callable($function)) { + $url = call_user_func($function, $relationship); + } + + if ($url == "") { + $nameid = $relationship->id; + + $url = elgg_get_site_url() . "export/$view/$guid/relationship/$nameid/"; + } + + return $url; + } + + return false; +} + +/**** 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 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; + } +} + +/** + * Function to get all objects attached to a particular object + * + * @param int $guid Entity GUID + * @param string $type The type of object to return e.g. 'file', 'friend_of' etc + * + * @return an array of objects + * @access private + */ +function get_attachments($guid, $type = "") { + $options = array( + 'relationship' => 'attached', + 'relationship_guid' => $guid, + 'inverse_relationship' => false, + 'types' => $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 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 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; + } + } +} + +/** + * 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']; + + $tmp = NULL; + + if ($element instanceof ODDRelationship) { + $tmp = new ElggRelationship(); + $tmp->import($element); + + return $tmp; + } +} + +/** + * 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 + * @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')); + } + + if (!is_array($returnvalue)) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonArrayReturnValue')); + } + + $guid = (int)$params['guid']; + + $result = get_entity_relationships($guid); + + if ($result) { + foreach ($result as $r) { + $returnvalue[] = $r->export(); + } + } + + return $returnvalue; +} + +/** + * Notify user that someone has friended them + * + * @param string $event Event name + * @param string $type Object type + * @param mixed $object Object + * + * @return bool + * @access private + */ +function relationship_notification_hook($event, $type, $object) { + + $user_one = get_entity($object->guid_one); + $user_two = get_entity($object->guid_two); + + 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 +elgg_register_plugin_hook_handler("import", "all", "import_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 +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..33f34360e --- /dev/null +++ b/engine/lib/river.php @@ -0,0 +1,700 @@ +<?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 && !retrieve_cached_entity($item->subject_guid)) { + $guids[$item->subject_guid] = true; + } + if ($item->object_guid && !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 && !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); + + $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 ''; + } + + $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 activiy + * + * @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); + + // content filter code here + $entity_type = ''; + $entity_subtype = ''; + + 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/sessions.php b/engine/lib/sessions.php new file mode 100644 index 000000000..72ca0a1c2 --- /dev/null +++ b/engine/lib/sessions.php @@ -0,0 +1,653 @@ +<?php + +/** + * Elgg session management + * Functions to manage logins + * + * @package Elgg.Core + * @subpackage Session + */ + +/** Elgg magic session */ +global $SESSION; + +/** + * 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. + * + * @return ElggUser + */ +function elgg_get_logged_in_user_entity() { + global $SESSION; + + if (isset($SESSION)) { + return $SESSION['user']; + } + + return NULL; +} + +/** + * Return the current logged in user by id. + * + * @see elgg_get_logged_in_user_entity() + * @return int + */ +function elgg_get_logged_in_user_guid() { + $user = elgg_get_logged_in_user_entity(); + if ($user) { + return $user->guid; + } + + return 0; +} + +/** + * Returns whether or not the user is currently logged in + * + * @return bool + */ +function elgg_is_logged_in() { + $user = elgg_get_logged_in_user_entity(); + + if ((isset($user)) && ($user instanceof ElggUser) && ($user->guid > 0)) { + return true; + } + + return false; +} + +/** + * Returns whether or not the user is currently logged in and that they are an admin user. + * + * @return bool + */ +function elgg_is_admin_logged_in() { + $user = elgg_get_logged_in_user_entity(); + + if ((elgg_is_logged_in()) && $user->isAdmin()) { + return TRUE; + } + + return FALSE; +} + +/** + * Check if the given user has full access. + * + * @todo: Will always return full access if the user is an admin. + * + * @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 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; + } + return FALSE; +} + +/** + * 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 + * @param string $password The password + * + * @return true|string True or an error message on failure + * @access private + */ +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 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 + * Elgg's PAM system. This function expects + * 'username' and 'password' (cleartext). + * + * @return bool + * @throws LoginException + * @access private + */ +function pam_auth_userpass(array $credentials = array()) { + + if (!isset($credentials['username']) || !isset($credentials['password'])) { + return false; + } + + $user = get_user_by_username($credentials['username']); + if (!$user) { + throw new LoginException(elgg_echo('LoginException:UsernameFailure')); + } + + 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 true; +} + +/** + * Log a failed login for $user_guid + * + * @param int $user_guid User GUID + * + * @return bool + */ +function log_login_failure($user_guid) { + $user_guid = (int)$user_guid; + $user = get_entity($user_guid); + + if (($user_guid) && ($user) && ($user instanceof ElggUser)) { + $fails = (int)$user->getPrivateSetting("login_failures"); + $fails++; + + $user->setPrivateSetting("login_failures", $fails); + $user->setPrivateSetting("login_failure_$fails", time()); + return true; + } + + return false; +} + +/** + * Resets the fail login count for $user_guid + * + * @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; + $user = get_entity($user_guid); + + if (($user_guid) && ($user) && ($user instanceof ElggUser)) { + $fails = (int)$user->getPrivateSetting("login_failures"); + + if ($fails) { + for ($n = 1; $n <= $fails; $n++) { + $user->removePrivateSetting("login_failure_$n"); + } + + $user->removePrivateSetting("login_failures"); + + return true; + } + + // nothing to reset + return true; + } + + return false; +} + +/** + * Checks if the rate limit of failed logins has been exceeded for $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 + $limit = 5; + $user_guid = (int)$user_guid; + $user = get_entity($user_guid); + + if (($user_guid) && ($user) && ($user instanceof ElggUser)) { + $fails = (int)$user->getPrivateSetting("login_failures"); + if ($fails >= $limit) { + $cnt = 0; + $time = time(); + for ($n = $fails; $n > 0; $n--) { + $f = $user->getPrivateSetting("login_failure_$n"); + if ($f > $time - (60 * 5)) { + $cnt++; + } + + if ($cnt == $limit) { + // Limit reached + return true; + } + } + } + } + + return false; +} + +/** + * Logs in a specified ElggUser. For standard registration, use in conjunction + * with elgg_authenticate. + * + * @see elgg_authenticate + * + * @param ElggUser $user A valid Elgg user object + * @param boolean $persistent Should this be a persistent login? + * + * @return true or throws exception + * @throws LoginException + */ +function login(ElggUser $user, $persistent = false) { + global $CONFIG; + + // User is banned, return false. + if ($user->isBanned()) { + throw new LoginException(elgg_echo('LoginException:BannedUser')); + } + + $_SESSION['user'] = $user; + $_SESSION['guid'] = $user->getGUID(); + $_SESSION['id'] = $_SESSION['guid']; + $_SESSION['username'] = $user->username; + $_SESSION['name'] = $user->name; + + // if remember me checked, set cookie with token and store token on user + if (($persistent)) { + $code = (md5($user->name . $user->username . time() . rand())); + $_SESSION['code'] = $code; + $user->code = md5($code); + setcookie("elggperm", $code, (time() + (86400 * 30)), "/"); + } + + 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)), "/"); + throw new LoginException(elgg_echo('LoginException:Unknown')); + } + + // Users privilege has been elevated, so change the session id (prevents session fixation) + session_regenerate_id(); + + // Update statistics + set_last_login($_SESSION['guid']); + reset_login_failure_count($user->guid); // Reset any previous failed login attempts + + return true; +} + +/** + * Log the current user out + * + * @return bool + */ +function logout() { + global $CONFIG; + + if (isset($_SESSION['user'])) { + if (!elgg_trigger_event('logout', 'user', $_SESSION['user'])) { + return false; + } + $_SESSION['user']->code = ""; + $_SESSION['user']->save(); + } + + unset($_SESSION['username']); + unset($_SESSION['name']); + unset($_SESSION['code']); + unset($_SESSION['guid']); + unset($_SESSION['id']); + unset($_SESSION['user']); + + setcookie("elggperm", "", (time() - (86400 * 30)), "/"); + + // pass along any messages + $old_msg = $_SESSION['msg']; + + session_destroy(); + + // starting a default session to store any post-logout messages. + _elgg_session_boot(NULL, NULL, NULL); + $_SESSION['msg'] = $old_msg; + + return TRUE; +} + +/** + * 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 + * + * @uses $_SESSION + * + * @return bool + * @access private + */ +function _elgg_session_boot() { + global $DB_PREFIX, $CONFIG; + + // 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_name('Elgg'); + session_start(); + + // Generate a simple token (private from potentially public session id) + if (!isset($_SESSION['__elgg_session'])) { + $_SESSION['__elgg_session'] = md5(microtime() . rand()); + } + + // test whether we have a user session + if (empty($_SESSION['guid'])) { + + // clear session variables before checking cookie + unset($_SESSION['user']); + 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 + $code = $_COOKIE['elggperm']; + $code = md5($code); + if ($user = get_user_by_code($code)) { + // we have a user, log him in + $_SESSION['user'] = $user; + $_SESSION['id'] = $user->getGUID(); + $_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 + if ($user = get_user($_SESSION['guid'])) { + $_SESSION['user'] = $user; + $_SESSION['id'] = $user->getGUID(); + $_SESSION['guid'] = $_SESSION['id']; + } else { + // user must have been deleted with a session active + unset($_SESSION['user']); + unset($_SESSION['id']); + unset($_SESSION['guid']); + unset($_SESSION['code']); + } + } + + if (isset($_SESSION['guid'])) { + set_last_action($_SESSION['guid']); + } + + elgg_register_action('login', '', 'public'); + elgg_register_action('logout'); + + // Register a default PAM handler + register_pam_handler('pam_auth_userpass'); + + // Initialise the magic session + global $SESSION; + $SESSION = new ElggSession(); + + // Finally we ensure that a user who has been banned with an open session is kicked. + if ((isset($_SESSION['user'])) && ($_SESSION['user']->isBanned())) { + session_destroy(); + return false; + } + + return true; +} + +/** + * Used at the top of a page to mark it as logged in users only. + * + * @return void + */ +function gatekeeper() { + if (!elgg_is_logged_in()) { + $_SESSION['last_forward_from'] = current_page_url(); + register_error(elgg_echo('loggedinrequired')); + 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 (!elgg_is_admin_logged_in()) { + $_SESSION['last_forward_from'] = current_page_url(); + register_error(elgg_echo('adminrequired')); + forward('', 'admin'); + } +} + +/** + * 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) { + global $sess_save_path; + $sess_save_path = $save_path; + + return true; +} + +/** + * Closes a session + * + * @todo implement + * @todo document + * + * @return true + * @access private + */ +function _elgg_session_close() { + return true; +} + +/** + * 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) { + global $DB_PREFIX; + + $id = sanitise_string($id); + + try { + $result = get_data_row("SELECT * from {$DB_PREFIX}users_sessions where session='$id'"); + + if ($result) { + return (string)$result->data; + } + + } catch (DatabaseException $e) { + + // Fall back to file store in this case, since this likely means + // that the database hasn't been upgraded + global $sess_save_path; + + $sess_file = "$sess_save_path/sess_$id"; + return (string) @file_get_contents($sess_file); + } + + return ''; +} + +/** + * 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) { + global $DB_PREFIX; + + $id = sanitise_string($id); + $time = time(); + + try { + $sess_data_sanitised = sanitise_string($sess_data); + + $q = "REPLACE INTO {$DB_PREFIX}users_sessions + (session, ts, data) VALUES + ('$id', '$time', '$sess_data_sanitised')"; + + if (insert_data($q) !== false) { + return true; + } + } catch (DatabaseException $e) { + // Fall back to file store in this case, since this likely means + // that the database hasn't been upgraded + global $sess_save_path; + + $sess_file = "$sess_save_path/sess_$id"; + if ($fp = @fopen($sess_file, "w")) { + $return = fwrite($fp, $sess_data); + fclose($fp); + return $return; + } + } + + return false; +} + +/** + * Destroy a DB session, falling back to file. + * + * @param string $id Session ID + * + * @return bool + * @access private + */ +function _elgg_session_destroy($id) { + global $DB_PREFIX; + + $id = sanitise_string($id); + + try { + return (bool)delete_data("DELETE from {$DB_PREFIX}users_sessions where session='$id'"); + } catch (DatabaseException $e) { + // Fall back to file store in this case, since this likely means that + // the database hasn't been upgraded + global $sess_save_path; + + $sess_file = "$sess_save_path/sess_$id"; + return(@unlink($sess_file)); + } + + return false; +} + +/** + * Perform garbage collection on session table / files + * + * @param int $maxlifetime Max age of a session + * + * @return bool + * @access private + */ +function _elgg_session_gc($maxlifetime) { + global $DB_PREFIX; + + $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 + global $sess_save_path; + + foreach (glob("$sess_save_path/sess_*") as $filename) { + if (filemtime($filename) < $life) { + @unlink($filename); + } + } + } + + return true; +} diff --git a/engine/lib/sites.php b/engine/lib/sites.php new file mode 100644 index 000000000..d9eb2d25e --- /dev/null +++ b/engine/lib/sites.php @@ -0,0 +1,260 @@ +<?php +/** + * Elgg sites + * Functions to manage multiple or single sites in an Elgg install + * + * @package Elgg.Core + * @subpackage DataModel.Site + */ + +/** + * Get an ElggSite entity (default is current site) + * + * @param int $site_guid Optional. Site GUID. + * + * @return ElggSite + * @since 1.8.0 + */ +function elgg_get_site_entity($site_guid = 0) { + global $CONFIG; + + $result = false; + + if ($site_guid == 0) { + $site = $CONFIG->site; + } else { + $site = get_entity($site_guid); + } + + if($site instanceof ElggSite){ + $result = $site; + } + + return $result; +} + +/** + * Return the site specific details of a site by a row. + * + * @param int $guid The site GUID + * + * @return mixed + * @access private + */ +function get_site_entity_as_row($guid) { + global $CONFIG; + + $guid = (int)$guid; + return get_data_row("SELECT * from {$CONFIG->dbprefix}sites_entity where guid=$guid"); +} + +/** + * Create or update the entities table for a given site. + * Call create_entity first. + * + * @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; + + $guid = (int)$guid; + $name = sanitise_string($name); + $description = sanitise_string($description); + $url = sanitise_string($url); + + $row = get_entity_as_row($guid); + + if ($row) { + // Exists and you have access to it + $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 (elgg_trigger_event('update', $entity->type, $entity)) { + return $guid; + } else { + $entity->delete(); + //delete_entity($guid); + } + } + } else { + // Update failed, attempt an insert. + $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 (elgg_trigger_event('create', $entity->type, $entity)) { + return $guid; + } else { + $entity->delete(); + //delete_entity($guid); + } + } + } + } + + return false; +} + +/** + * Add a user to a site. + * + * @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; + + return add_entity_relationship($user_guid, "member_of_site", $site_guid); +} + +/** + * Remove a user from a site. + * + * @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; + $user_guid = (int)$user_guid; + + return remove_entity_relationship($user_guid, "member_of_site", $site_guid); +} + +/** + * Add an object to a site. + * + * @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; + + return add_entity_relationship($object_guid, "member_of_site", $site_guid); +} + +/** + * Remove an object from a site. + * + * @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; + $object_guid = (int)$object_guid; + + return remove_entity_relationship($object_guid, "member_of_site", $site_guid); +} + +/** + * Get the objects belonging to a site. + * + * @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; + $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 + )); +} + +/** + * Return the site via a url. + * + * @param string $url The URL of a site + * + * @return mixed + */ +function get_site_by_url($url) { + global $CONFIG; + + $url = sanitise_string($url); + + $row = get_data_row("SELECT * from {$CONFIG->dbprefix}sites_entity where url='$url'"); + + if ($row) { + return get_entity($row->guid); + } + + return false; +} + +/** + * Retrieve a site and return the domain portion of its url. + * + * @param int $guid ElggSite GUID + * + * @return string + */ +function get_site_domain($guid) { + $guid = (int)$guid; + + $site = get_entity($guid); + if ($site instanceof ElggSite) { + $breakdown = parse_url($site->url); + return $breakdown['host']; + } + + return false; +} + +/** + * Unit tests for sites + * + * @param sting $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params + * + * @return array + * @access private + */ +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/statistics.php b/engine/lib/statistics.php new file mode 100644 index 000000000..5ee640549 --- /dev/null +++ b/engine/lib/statistics.php @@ -0,0 +1,121 @@ +<?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. + * + * @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) { + global $CONFIG; + + $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"; + + $owner_query = ""; + if ($owner_guid) { + $query .= " where owner_guid=$owner_guid"; + $owner_query = "and owner_guid=$owner_guid "; + } + + // Get a list of major types + + $types = get_data($query); + foreach ($types as $type) { + // assume there are subtypes for now + if (!is_array($entity_stats[$type->type])) { + $entity_stats[$type->type] = array(); + } + + $query = "SELECT count(*) as count + from {$CONFIG->dbprefix}entities where type='{$type->type}' $owner_query"; + + if ($type->subtype) { + $query .= " and subtype={$type->subtype_id}"; + } + + $subtype_cnt = get_data_row($query); + + if ($type->subtype) { + $entity_stats[$type->type][$type->subtype] = $subtype_cnt->count; + } else { + $entity_stats[$type->type]['__base__'] = $subtype_cnt->count; + } + } + + return $entity_stats; +} + +/** + * Return the number of users registered in the system. + * + * @param bool $show_deactivated Count not enabled users? + * + * @return int + */ +function get_number_users($show_deactivated = false) { + global $CONFIG; + + $access = ""; + + if (!$show_deactivated) { + $access = "and " . get_access_sql_suffix(); + } + + $query = "SELECT count(*) as count + from {$CONFIG->dbprefix}entities where type='user' $access"; + + $result = get_data_row($query); + + if ($result) { + return $result->count; + } + + return false; +} + +/** + * Return a list of how many users are currently online, rendered as a view. + * + * @return string + */ +function get_online_users() { + $count = find_active_users(600, 10, 0, true); + $objects = find_active_users(600, 10); + + if ($objects) { + return elgg_view_entity_list($objects, array( + 'count' => $count, + 'limit' => 10 + )); + } +} + +/** + * Initialise the statistics admin page. + * + * @return void + * @access private + */ +function statistics_init() { + elgg_extend_view('core/settings/statistics', 'core/settings/statistics/online'); + elgg_extend_view('core/settings/statistics', 'core/settings/statistics/numentities'); +} + +/// Register init function +elgg_register_event_handler('init', 'system', 'statistics_init'); diff --git a/engine/lib/system_log.php b/engine/lib/system_log.php new file mode 100644 index 000000000..53fa24557 --- /dev/null +++ b/engine/lib/system_log.php @@ -0,0 +1,299 @@ +<?php +/** + * Elgg system log. + * Listens to events and writes crud events into the system log database. + * + * @package Elgg.Core + * @subpackage Logging + */ + +/** + * Retrieve the system log based on a number of parameters. + * + * @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 str $ip_address The IP address. + * @return mixed + */ +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 = false) { + + global $CONFIG; + + $by_user_orig = $by_user; + if (is_array($by_user) && sizeof($by_user) > 0) { + 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 !== "" && $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) . ")"; + } + } + if ($event != "") { + $where[] = "event='$event'"; + } + if ($class !== "") { + $where[] = "object_class='$class'"; + } + if ($type != "") { + $where[] = "object_type='$type'"; + } + if ($subtype !== "") { + $where[] = "object_subtype='$subtype'"; + } + + if ($timebefore) { + $where[] = "time_created < " . ((int) $timebefore); + } + if ($timeafter) { + $where[] = "time_created > " . ((int) $timeafter); + } + if ($object_id) { + $where[] = "object_id = " . ((int) $object_id); + } + if ($ip_address) { + $where[] = "ip_address = '$ip_address'"; + } + + $select = "*"; + if ($count) { + $select = "count(*) as count"; + } + $query = "SELECT $select from {$CONFIG->dbprefix}system_log where 1 "; + foreach ($where as $w) { + $query .= " and $w"; + } + + if (!$count) { + $query .= " order by time_created desc"; + $query .= " limit $offset, $limit"; // Add order and limit + } + + if ($count) { + $numrows = get_data_row($query); + if ($numrows) { + return $numrows->count; + } + } else { + return get_data($query); + } + + return false; +} + +/** + * Return a specific log entry. + * + * @param int $entry_id The log entry + * + * @return mixed + */ +function get_log_entry($entry_id) { + global $CONFIG; + + $entry_id = (int)$entry_id; + + return get_data_row("SELECT * from {$CONFIG->dbprefix}system_log where id=$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; + // surround with try/catch because object could be disabled + try { + $object = new $class($entry->object_id); + } catch (Exception $e) { + + } + if ($object) { + return $object; + } + } + + return false; +} + +/** + * Log a system event related to a specific object. + * + * This is called by the event system and should not be called directly. + * + * @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 $log_cache; + static $cache_size = 0; + + if ($object instanceof Loggable) { + + 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 + $object_id = (int)$object->getSystemLogID(); + $object_class = $object->getClassName(); + $object_type = $object->getType(); + $object_subtype = $object->getSubtype(); + $event = sanitise_string($event); + $time = time(); + $ip_address = sanitise_string($_SERVER['REMOTE_ADDR']); + $performed_by = elgg_get_logged_in_user_guid(); + + if (isset($object->access_id)) { + $access_id = $object->access_id; + } else { + $access_id = ACCESS_PUBLIC; + } + if (isset($object->enabled)) { + $enabled = $object->enabled; + } else { + $enabled = 'yes'; + } + + if (isset($object->owner_guid)) { + $owner_guid = $object->owner_guid; + } else { + $owner_guid = 0; + } + + // Create log if we haven't already created it + 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; + } + } +} + +/** + * 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; + + $offset = (int)$offset; + $now = time(); // Take a snapshot of now + + $ts = $now - $offset; + + // create table + $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; + } + + // delete + // Don't delete on time since we are running in a concurrent environment + if (delete_data("DELETE from {$CONFIG->dbprefix}system_log WHERE time_created<$ts") === false) { + return false; + } + + // alter table to engine + if (!update_data("ALTER TABLE {$CONFIG->dbprefix}system_log_$now engine=archive")) { + return false; + } + + return true; +} + +/** + * Default system log handler, allows plugins to override, extend or disable logging. + * + * @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']); + + return true; +} + +/** + * System log listener. + * This function listens to all events in the system and logs anything appropriate. + * + * @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')) { + elgg_trigger_event('log', 'systemlog', array('object' => $object, 'event' => $event)); + } + + return true; +} + +/** Register event to listen to all events **/ +elgg_register_event_handler('all', 'all', 'system_log_listener', 400); + +/** Register a default system log handler */ +elgg_register_event_handler('log', 'systemlog', 'system_log_default_logger', 999); diff --git a/engine/lib/tags.php b/engine/lib/tags.php new file mode 100644 index 000000000..a0887d0f3 --- /dev/null +++ b/engine/lib/tags.php @@ -0,0 +1,353 @@ +<?php +/** + * Elgg tags + * Functions for managing tags and tag clouds. + * + * @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); + $thresholds = array(); + + for ($n = 1; $n <= $buckets; $n++) { + $thresholds[$n - 1] = ($min + $n) * $delta; + } + + // Correction + if ($thresholds[$buckets - 1] > $max) { + $thresholds[$buckets - 1] = $max; + } + + $size = 0; + for ($n = 0; $n < count($thresholds); $n++) { + if ($number_of_tags >= $thresholds[$n]) { + $size = $n; + } + } + + return $size; +} + +/** + * This function generates an array of tags with a weighting. + * + * @param array $tags The array of tags. + * @param int $buckets The number of buckets + * + * @return 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(); + + $min = 65535; + $max = 0; + + foreach ($tags as $tag) { + $cloud[$tag]++; + + if ($cloud[$tag] > $max) { + $max = $cloud[$tag]; + } + + if ($cloud[$tag] < $min) { + $min = $cloud[$tag]; + } + } + + foreach ($cloud as $k => $v) { + $cloud[$k] = calculate_tag_size($min, $max, $v, $buckets); + } + + return $cloud; +} + +/** + * 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 false/array - if no tags or error, false + * otherwise, array of objects with ->tag and ->total values + * @since 1.7.1 + */ +function elgg_get_tags(array $options = array()) { + global $CONFIG; + + $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 (!is_array($options['tag_names'])) { + return false; + } + + // empty array so use all registered tag names + if (count($options['tag_names']) == 0) { + $options['tag_names'] = $registered_tags; + } + + $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; + } + + + $wheres = $options['wheres']; + + // catch for tags that were spaces + $wheres[] = "msv.string != ''"; + + foreach ($options['tag_names'] as $tag) { + $sanitised_tags[] = '"' . sanitise_string($tag) . '"'; + } + $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]); + } + } + + // 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]); + } + } + + + $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 "; + } + + // add wheres + $query .= ' WHERE '; + + foreach ($wheres as $w) { + $query .= " $w AND "; + } + + // Add access controls + $query .= get_access_sql_suffix('e'); + + $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); +} + +/** + * 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()) { + + $type = $subtype = ''; + if (isset($options['type'])) { + $type = $options['type']; + } + if (isset($options['subtype'])) { + $subtype = $options['subtype']; + } + + $tag_data = elgg_get_tags($options); + return elgg_view("output/tagcloud", array( + 'value' => $tag_data, + 'type' => $type, + 'subtype' => $subtype, + )); +} + +/** + * Registers a metadata name as containing tags for an entity. + * This is required if you are using a non-standard metadata name + * for your tags. + * + * @param string $name Tag name + * + * @return bool + * @since 1.7.0 + */ +function elgg_register_tag_metadata_name($name) { + global $CONFIG; + + if (!isset($CONFIG->registered_tag_metadata_names)) { + $CONFIG->registered_tag_metadata_names = array(); + } + + if (!in_array($name, $CONFIG->registered_tag_metadata_names)) { + $CONFIG->registered_tag_metadata_names[] = $name; + } + + return TRUE; +} + +/** + * Returns an array of valid metadata names for tags. + * + * @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(); + + return $names; +} + +/** + * 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..f4f4b16f5 --- /dev/null +++ b/engine/lib/upgrade.php @@ -0,0 +1,368 @@ +<?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) { + 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 http://trac.elgg.org/elgg/ticket/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', + ); + + $upgrades_17 = array(); + $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, $DB_QUERY_CACHE; + + $is_locked = count(get_data("show tables like '{$CONFIG->dbprefix}upgrade_lock'")); + + // Invalidate query cache + if ($DB_QUERY_CACHE) { + $DB_QUERY_CACHE->clear(); + elgg_log("Query cache invalidated", 'NOTICE'); + } + + return $is_locked; +} diff --git a/engine/lib/upgrades/2008100701.php b/engine/lib/upgrades/2008100701.php new file mode 100644 index 000000000..b8d4dfdbc --- /dev/null +++ b/engine/lib/upgrades/2008100701.php @@ -0,0 +1,7 @@ +<?php + +/** + * 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 new file mode 100644 index 000000000..69e44e3a0 --- /dev/null +++ b/engine/lib/upgrades/2008101303.php @@ -0,0 +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'); + } +} diff --git a/engine/lib/upgrades/2009022701.php b/engine/lib/upgrades/2009022701.php new file mode 100644 index 000000000..54083a34d --- /dev/null +++ b/engine/lib/upgrades/2009022701.php @@ -0,0 +1,7 @@ +<?php +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 new file mode 100644 index 000000000..7b31a3bc9 --- /dev/null +++ b/engine/lib/upgrades/2009041701.php @@ -0,0 +1,8 @@ +<?php + +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 new file mode 100644 index 000000000..d0eae9b91 --- /dev/null +++ b/engine/lib/upgrades/2009070101.php @@ -0,0 +1,9 @@ +<?php + +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 new file mode 100644 index 000000000..cab9a6835 --- /dev/null +++ b/engine/lib/upgrades/2009102801.php @@ -0,0 +1,221 @@ +<?php + +/** + * Move user's data directories from using username to registration date + */ + +/** + * 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) { + $matrix = ""; + + $len = strlen($username); + if ($len > 5) { + $len = 5; + } + + for ($n = 0; $n < $len; $n++) { + if (ctype_alnum($username[$n])) { + $matrix .= $username[$n] . "/"; + } + } + + return $matrix . $username . "/"; +} + + +/** + * 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) { + $matrix = ""; + + $name = $filename; + $filename = mb_str_split($filename); + if (!$filename) { + return false; + } + + $len = count($filename); + if ($len > 5) { + $len = 5; + } + + for ($n = 0; $n < $len; $n++) { + $matrix .= $filename[$n] . "/"; + } + + return $matrix . $name . "/"; +} + +/** + * 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) { + $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; +} + + +/** + * 1.6 style file matrix + * + * @param string $filename The filename + * + * @return string + */ +function file_matrix_1_6($filename) { + $invalid_fs_chars = '*\'\\/"!$%^&*.%(){}[]#~?<>;|¬`@-+='; + + $matrix = ""; + + $name = $filename; + $filename = mb_str_split($filename); + if (!$filename) { + return false; + } + + $len = count($filename); + 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) { + $char = '_'; + } + + $matrix .= $char . "/"; + } + + return $matrix . $name . "/"; +} + + +/** + * 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 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') { + 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"); + } + } else { + //elgg_dump("Ignoring $from_path -> $to_path"); + } + } +} + +/** + * 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') { + // only to be used for user directories + return FALSE; + } + + $time_created = date('Y/m/d', $user->time_created); + return "$time_created/$user->guid/"; +} + +global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE; +/** + * Upgrade file locations + */ +$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(); + + $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/2010010501.php b/engine/lib/upgrades/2010010501.php new file mode 100644 index 000000000..1e83caa55 --- /dev/null +++ b/engine/lib/upgrades/2010010501.php @@ -0,0 +1,8 @@ +<?php + +global $CONFIG; + +/** + * Enable the search plugin + */ +enable_plugin('search', $CONFIG->site->guid); diff --git a/engine/lib/upgrades/2010033101.php b/engine/lib/upgrades/2010033101.php new file mode 100644 index 000000000..0bffee001 --- /dev/null +++ b/engine/lib/upgrades/2010033101.php @@ -0,0 +1,70 @@ +<?php + +/** + * Conditional upgrade for UTF8 as described in http://trac.elgg.org/ticket/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..9ff7d3102 --- /dev/null +++ b/engine/lib/upgrades/2010061501.php @@ -0,0 +1,73 @@ +<?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 $DB_QUERY_CACHE, $DB_PROFILE, $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)) { + $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + + $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..1b5d379d8 --- /dev/null +++ b/engine/lib/upgrades/2010071001.php @@ -0,0 +1,57 @@ +<?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 $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +$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(); + + $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..30bd6538c --- /dev/null +++ b/engine/lib/upgrades/2010071002.php @@ -0,0 +1,49 @@ +<?php +/** + * Update the notifications based on all friends and access collections + */ + +// loop through all users checking collections and notifications +global $DB_QUERY_CACHE, $DB_PROFILE, $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)) { + $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + + $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..8084bc06c --- /dev/null +++ b/engine/lib/upgrades/2011052801.php @@ -0,0 +1,45 @@ +<?php +/** + * Make sure all users have the relationship member_of_site + */ +global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +$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) { + $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + + // 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..07732f261 --- /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 http://trac.elgg.org/ticket/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/create_upgrade.php b/engine/lib/upgrades/create_upgrade.php new file mode 100644 index 000000000..3652e18a2 --- /dev/null +++ b/engine/lib/upgrades/create_upgrade.php @@ -0,0 +1,151 @@ +<?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 (!fputs($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); + + fputs($h, $out); + fclose($h); +} + +/** + * 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..e4069fb53 --- /dev/null +++ b/engine/lib/user_settings.php @@ -0,0 +1,359 @@ +<?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() { + if (elgg_get_context() == "settings") { + $user = elgg_get_page_owner_entity(); + + $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_guid(); + 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; + } +} + +/** + * 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 new file mode 100644 index 000000000..95ef9d176 --- /dev/null +++ b/engine/lib/users.php @@ -0,0 +1,1607 @@ +<?php +/** + * Elgg users + * Functions to manage multiple or single users in an Elgg install + * + * @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(); + +/** + * Return the user specific details of a user by a row. + * + * @param int $guid The ElggUser guid + * + * @return mixed + * @access private + */ +function get_user_entity_as_row($guid) { + global $CONFIG; + + $guid = (int)$guid; + return get_data_row("SELECT * from {$CONFIG->dbprefix}users_entity where guid=$guid"); +} + +/** + * Create or update the entities table for a given user. + * Call create_entity first. + * + * @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; + + $guid = (int)$guid; + $name = sanitise_string($name); + $username = sanitise_string($username); + $password = sanitise_string($password); + $salt = sanitise_string($salt); + $email = sanitise_string($email); + $language = sanitise_string($language); + $code = sanitise_string($code); + + $row = get_entity_as_row($guid); + if ($row) { + // Exists and you have access to it + $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 (elgg_trigger_event('update', $entity->type, $entity)) { + return $guid; + } else { + $entity->delete(); + } + } + } else { + // 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 (elgg_trigger_event('create', $entity->type, $entity)) { + return $guid; + } else { + $entity->delete(); + } + } + } + } + + return false; +} + +/** + * Disables all of a user's entities + * + * @param int $owner_guid The owner GUID + * + * @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 (elgg_trigger_event('disable', $entity->type, $entity)) { + if ($entity->canEdit()) { + $query = "UPDATE {$CONFIG->dbprefix}entities + set enabled='no' where owner_guid={$owner_guid} + or container_guid = {$owner_guid}"; + + $res = update_data($query); + return $res; + } + } + } + + return false; +} + +/** + * Ban a user + * + * @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; + + $user = get_entity($user_guid); + + if (($user) && ($user->canEdit()) && ($user instanceof ElggUser)) { + if (elgg_trigger_event('ban', 'user', $user)) { + // Add reason + if ($reason) { + create_metadata($user_guid, 'ban_reason', $reason, '', 0, ACCESS_PUBLIC); + } + + // clear "remember me" cookie code so user cannot login in using it + $user->code = ""; + $user->save(); + + // 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); + } + + // Set ban flag + $query = "UPDATE {$CONFIG->dbprefix}users_entity set banned='yes' where guid=$user_guid"; + return update_data($query); + } + + return FALSE; + } + + return FALSE; +} + +/** + * Unban a user. + * + * @param int $user_guid Unban a user. + * + * @return bool + */ +function unban_user($user_guid) { + global $CONFIG; + + $user_guid = (int)$user_guid; + + $user = get_entity($user_guid); + + if (($user) && ($user->canEdit()) && ($user instanceof ElggUser)) { + if (elgg_trigger_event('unban', 'user', $user)) { + create_metadata($user_guid, 'ban_reason', '', '', 0, ACCESS_PUBLIC); + + // 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); + } + + + $query = "UPDATE {$CONFIG->dbprefix}users_entity set banned='no' where guid=$user_guid"; + return update_data($query); + } + + 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"); + invalidate_cache_for_entity($user_guid); + return $r; + } + + return FALSE; + } + + return FALSE; +} + +/** + * Removes user $guid's admin flag. + * + * @param int $user_guid User GUID + * + * @return bool + */ +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'); + } + + if ($newentity_cache) { + $newentity_cache->delete($user_guid); + } + + $r = update_data("UPDATE {$CONFIG->dbprefix}users_entity set admin='no' where guid=$user_guid"); + 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 + */ +function get_user_sites($user_guid, $limit = 10, $offset = 0) { + $user_guid = (int)$user_guid; + $limit = (int)$limit; + $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', + 'limit' => $limit, + 'offset' => $offset, + )); +} + +/** + * Adds a user to another user's friends list. + * + * @param int $user_guid The GUID of the friending user + * @param int $friend_guid The GUID of the user to friend + * + * @return bool Depending on success + */ +function user_add_friend($user_guid, $friend_guid) { + $user_guid = (int) $user_guid; + $friend_guid = (int) $friend_guid; + if ($user_guid == $friend_guid) { + return false; + } + if (!$friend = get_entity($friend_guid)) { + return false; + } + if (!$user = get_entity($user_guid)) { + return false; + } + if ((!($user instanceof ElggUser)) || (!($friend instanceof ElggUser))) { + return false; + } + return add_entity_relationship($user_guid, "friend", $friend_guid); +} + +/** + * Removes a user from another user's friends list. + * + * @param int $user_guid The GUID of the friending user + * @param int $friend_guid The GUID of the user on the friends list + * + * @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); + if ($collections) { + foreach ($collections as $collection) { + remove_user_from_access_collection($friend_guid, $collection->id); + } + } + + return remove_entity_relationship($user_guid, "friend", $friend_guid); +} + +/** + * Determines whether or not a user is another user's friend. + * + * @param int $user_guid The GUID of the user + * @param int $friend_guid The GUID of the friend + * + * @return bool + */ +function user_is_friend($user_guid, $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 + */ +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, + 'limit' => $limit, + 'offset' => $offset + )); +} + +/** + * 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 + */ +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'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) { + $friendguids[] = $friend->getGUID(); + } + return elgg_get_entities(array( + 'type' => 'object', + 'subtype' => $subtype, + 'owner_guids' => $friendguids, + 'limit' => $limit, + 'offset' => $offset, + 'container_guids' => $friendguids, + 'created_time_lower' => $timelower, + 'created_time_upper' => $timeupper + )); + } + return FALSE; +} + +/** + * 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 + * + * @return int The number of objects + */ +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) { + $friendguids[] = $friend->getGUID(); + } + return elgg_get_entities(array( + 'type' => 'object', + 'subtype' => $subtype, + 'owner_guids' => $friendguids, + 'count' => TRUE, + 'container_guids' => $friendguids, + 'created_time_lower' => $timelower, + 'created_time_upper' => $timeupper + )); + } + return 0; +} + +/** + * Displays a list of a user's friends' 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 $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, $full_view = true, +$listtypetoggle = 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); + + 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) { + // Fixes "Exception thrown without stack frame" when db_select fails + if (!empty($guid)) { + $result = get_entity($guid); + } + + if ((!empty($result)) && (!($result instanceof ElggUser))) { + return false; + } + + if (!empty($result)) { + return $result; + } + + return false; +} + +/** + * 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; + + $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]); + } + + $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 $entity; +} + +/** + * Get user by session code + * + * @param string $code The session code + * + * @return ElggUser|false Depending on success + */ +function get_user_by_code($code) { + global $CONFIG, $CODE_TO_GUID_MAP_CACHE; + + $code = sanitise_string($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]); + } + + $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 $entity; +} + +/** + * Get an array of users from an email address + * + * @param string $email Email address. + * + * @return array + */ +function get_user_by_email($email) { + global $CONFIG; + + $email = sanitise_string($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"; + + return get_data($query, 'entity_row_to_elggstar'); +} + +/** + * A function that returns a maximum of $limit users who have done something within the last + * $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. + * + * @return mixed + */ +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; + + $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 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) { + // generate code + $code = generate_random_cleartext_password(); + $user->setPrivateSetting('passwd_conf_code', $code); + + + // generate link + $link = $CONFIG->site->url . "resetpassword?u=$user_guid&c=$code"; + + // generate email + $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 false; +} + +/** + * Low level function to reset a given user's password. + * + * This can only be called from execute_new_password_request(). + * + * @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; + + $user = get_entity($user_guid); + + if ($user) { + $salt = generate_random_cleartext_password(); // Reset the salt + $user->salt = $salt; + + $hash = generate_user_password($user, $password); + + $query = "UPDATE {$CONFIG->dbprefix}users_entity + set password='$hash', salt='$salt' where guid=$user_guid"; + return update_data($query); + } + + return false; +} + +/** + * Validate and execute a password reset for a user. + * + * @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; + + $user_guid = (int)$user_guid; + $user = get_entity($user_guid); + + if ($user) { + $saved_code = $user->getPrivateSetting('passwd_conf_code'); + + 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'); + // clean the logins failures + reset_login_failure_count($user_guid); + + $email = elgg_echo('email:resetpassword:body', array($user->name, $password)); + + return notify_user($user->guid, $CONFIG->site->guid, + elgg_echo('email:resetpassword:subject'), $email, NULL, 'email'); + } + } + } + + return FALSE; +} + +/** + * 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() { + return substr(md5(microtime() . rand()), 0, 8); +} + +/** + * Generate a password for a user, currently uses MD5. + * + * @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); +} + +/** + * Simple function which ensures that a username contains only valid characters. + * + * This should only permit chars that are valid on the file system as well. + * + * @param string $username Username + * + * @return bool + * @throws RegistrationException on invalid + */ +function validate_username($username) { + global $CONFIG; + + // Basic, check length + if (!isset($CONFIG->minusername)) { + $CONFIG->minusername = 4; + } + + if (strlen($username) < $CONFIG->minusername) { + $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 + ']/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) { + $msg = elgg_echo('registration:invalidchars', array($blacklist2[$n], $blacklist2)); + $msg = htmlentities($msg, ENT_COMPAT, 'UTF-8'); + throw new RegistrationException($msg); + } + } + + $result = true; + return elgg_trigger_plugin_hook('registeruser:validate:username', 'all', + array('username' => $username), $result); +} + +/** + * Simple validation of a password. + * + * @param string $password Clear text password + * + * @return bool + * @throws RegistrationException on invalid + */ +function validate_password($password) { + 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 elgg_trigger_plugin_hook('registeruser:validate:password', 'all', + array('password' => $password), $result); +} + +/** + * Simple validation of a email. + * + * @param string $address Email address + * + * @throws RegistrationException on invalid + * @return bool + */ +function validate_email_address($address) { + if (!is_email_address($address)) { + throw new RegistrationException(elgg_echo('registration:notemail')); + } + + // Got here, so lets try a hook (defaulting to ok) + $result = true; + 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 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 + */ +function register_user($username, $password, $name, $email, +$allow_multiple_emails = false, $friend_guid = 0, $invitecode = '') { + + // Load the configuration + global $CONFIG; + + // no need to trim password. + $username = trim($username); + $name = trim(strip_tags($name)); + $email = trim($email); + + // A little sanity checking + if (empty($username) + || empty($password) + || empty($name) + || empty($email)) { + return false; + } + + // 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); + + if (!validate_email_address($email)) { + throw new RegistrationException(elgg_echo('registration:emailnotvalid')); + } + + if (!validate_password($password)) { + throw new RegistrationException(elgg_echo('registration:passwordnotvalid')); + } + + if (!validate_username($username)) { + throw new RegistrationException(elgg_echo('registration:usernamenotvalid')); + } + + if ($user = get_user_by_username($username)) { + throw new RegistrationException(elgg_echo('registration:userexists')); + } + + if ((!$allow_multiple_emails) && (get_user_by_email($email))) { + throw new RegistrationException(elgg_echo('registration:dupeemail')); + } + + access_show_hidden_entities($access_status); + + // Create user + $user = new ElggUser(); + $user->username = $username; + $user->email = $email; + $user->name = $name; + $user->access_id = ACCESS_PUBLIC; + $user->salt = generate_random_cleartext_password(); // Note salt generated before password! + $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 + if ($friend_guid) { + if ($friend_user = get_user($friend_guid)) { + if ($invitecode == generate_invite_code($friend_user->username)) { + $user->addFriend($friend_guid); + $friend_user->addFriend($user->guid); + + // @todo Should this be in addFriend? + add_to_river('river/relationship/friend/create', 'friend', $user->getGUID(), $friend_guid); + add_to_river('river/relationship/friend/create', 'friend', $friend_guid, $user->getGUID()); + } + } + } + + // Turn on email notifications by default + set_user_notification_setting($user->getGUID(), 'email', true); + + return $user->getGUID(); +} + +/** + * 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) { + $secret = datalist_get('__site_secret__'); + return md5($username . $secret); +} + +/** + * 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 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; + } +} + +/** + * 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 elgg_get_user_validation_status($user_guid) { + $md = elgg_get_metadata(array( + 'guid' => $user_guid, + 'metadata_name' => 'validated' + )); + if ($md == false) { + return; + } + + if ($md[0]->value) { + return true; + } + + 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-related pages + * + * @param array $segments URL segments + * @param string $handler The first segment in URL used for routing + * + * @return bool + * @access private + */ +function friends_page_handler($page_elements, $handler) { + elgg_set_context('friends'); + + if (isset($page_elements[0]) && $user = get_user_by_username($page_elements[0])) { + elgg_set_page_owner_guid($user->getGUID()); + } + if (elgg_get_logged_in_user_guid() == elgg_get_page_owner_guid()) { + collections_submenu_items(); + } + + 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 collections + * + * @param array $page_elements Page elements + * + * @return bool + * @access private + */ +function collections_page_handler($page_elements) { + elgg_set_context('friends'); + $base = elgg_get_config('path'); + if (isset($page_elements[0])) { + if ($page_elements[0] == "add") { + elgg_set_page_owner_guid(elgg_get_logged_in_user_guid()); + collections_submenu_items(); + require_once "{$base}pages/friends/collections/add.php"; + return true; + } else { + $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 "{$base}pages/friends/collections/view.php"; + return true; + } + } + } + return false; +} + +/** + * Page handler for account related pages + * + * @param array $page_elements Page elements + * @param string $handler The handler string + * + * @return bool + * @access private + */ +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(); + + $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(); + + $query = "UPDATE {$CONFIG->dbprefix}users_entity + set prev_last_login = last_login, last_login = {$time} where guid = {$user_guid}"; + + execute_delayed_write_query($query); +} + +/** + * Creates a relationship between this site and the user. + * + * @param string $event create + * @param string $object_type user + * @param ElggUser $object User object + * + * @return bool + * @access private + */ +function user_create_hook_add_site_relationship($event, $object_type, $object) { + global $CONFIG; + + add_entity_relationship($object->getGUID(), 'member_of_site', $CONFIG->site->getGUID()); +} + +/** + * 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']; + + 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 $return; +} + +/** + * Setup the menu shown with an entity + * + * @return array + * @access private + */ +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; + } + + 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)) { + $options = array( + 'name' => 'location', + 'text' => "<span>$entity->location</span>", + 'href' => false, + 'priority' => 150, + ); + $return[] = ElggMenuItem::factory($options); + } + } + + return $return; +} + +/** + * 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 elgg_profile_fields_setup() { + global $CONFIG; + + $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"))); + } + } +} + +/** + * Avatar page handler + * + * /avatar/edit/<username> + * /avatar/view/<username>/<size>/<icontime> + * + * @param array $page + * @return bool + * @access private + */ +function elgg_avatar_page_handler($page) { + global $CONFIG; + + $user = get_user_by_username($page[1]); + if ($user) { + elgg_set_page_owner_guid($user->getGUID()); + } + + 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; +} + +/** + * Profile page handler + * + * @param array $page + * @return bool + * @access private + */ +function elgg_profile_page_handler($page) { + global $CONFIG; + + $user = get_user_by_username($page[0]); + elgg_set_page_owner_guid($user->guid); + + if ($page[1] == 'edit') { + require_once("{$CONFIG->path}pages/profile/edit.php"); + return true; + } + return false; +} + +/** + * Sets up user-related menu items + * + * @return void + * @access private + */ +function users_pagesetup() { + + $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'), + )); + + elgg_register_menu_item('page', array( + 'name' => 'edit_profile', + 'href' => "profile/{$owner->username}/edit", + 'text' => elgg_echo('profile:edit'), + 'contexts' => array('profile_edit'), + )); + } + + // 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', + )); + + elgg_register_menu_item('topbar', array( + 'name' => 'friends', + 'href' => "friends/{$viewer->username}", + 'text' => elgg_view_icon('users'), + 'title' => elgg_echo('friends'), + 'priority' => 300, + )); + + 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', + )); + + elgg_register_menu_item('topbar', array( + 'name' => 'logout', + 'href' => "action/logout", + 'text' => elgg_echo('logout'), + 'is_action' => TRUE, + 'priority' => 1000, + 'section' => 'alt', + )); + } +} + +/** + * Users initialisation function, which establishes the page handler + * + * @return void + * @access private + */ +function users_init() { + + 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'); + + elgg_register_plugin_hook_handler('register', 'menu:user_hover', 'elgg_user_hover_menu'); + + 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'); + + 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 sting $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; + $value[] = "{$CONFIG->path}engine/tests/objects/users.php"; + return $value; +} + +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/views.php b/engine/lib/views.php new file mode 100644 index 000000000..01291b889 --- /dev/null +++ b/engine/lib/views.php @@ -0,0 +1,1657 @@ +<?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 {@trac #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 {@trac 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 (!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); + + 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 ''; + } + // Don't hide objects in closed groups that a user can see. + // see http://trac.elgg.org/ticket/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 object $item ElggEntity or ElggAnnotation + * @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; + + // 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]; +} + +/** + * 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 (!$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/web_services.php b/engine/lib/web_services.php new file mode 100644 index 000000000..c8e4a13cc --- /dev/null +++ b/engine/lib/web_services.php @@ -0,0 +1,1443 @@ +<?php +/** + * Elgg web services API + * Functions and objects for exposing custom web services. + * + * @package Elgg.Core + * @subpackage WebServicesAPI + */ + +// Primary Services API Server functions + +/** + * A global array holding API methods. + * The structure of this is + * $API_METHODS = array ( + * $method => array ( + * "description" => "Some human readable description" + * "function" = 'my_function_callback' + * "parameters" = array ( + * "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' + * "require_api_auth" => true | false (default) + * "require_user_auth" => true | false (default) + * ) + * ) + */ +global $API_METHODS; +$API_METHODS = array(); + +/** + * Expose a function as a services api call. + * + * Limitations: Currently cannot expose functions which expect objects. + * 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 + * 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) { + + global $API_METHODS; + + if (($method == "") || ($function == "")) { + $msg = elgg_echo('InvalidParameterException:APIMethodOrFunctionNotSet'); + throw new InvalidParameterException($msg); + } + + // does not check whether this method has already been exposed - good idea? + $API_METHODS[$method] = array(); + + $API_METHODS[$method]["description"] = $description; + + // does not check whether callable - done in execute_method() + $API_METHODS[$method]["function"] = $function; + + if ($parameters != NULL) { + if (!is_array($parameters)) { + $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)) { + $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method)); + throw new InvalidParameterException($msg); + } + } + + if ($parameters != NULL) { + // ensure the required flag is set correctly in default case for each parameter + foreach ($parameters as $key => $value) { + // check if 'required' was specified - if not, make it true + if (!array_key_exists('required', $value)) { + $parameters[$key]['required'] = true; + } + } + + $API_METHODS[$method]["parameters"] = $parameters; + } + + $call_method = strtoupper($call_method); + switch ($call_method) { + case 'POST' : + $API_METHODS[$method]["call_method"] = 'POST'; + break; + case 'GET' : + $API_METHODS[$method]["call_method"] = 'GET'; + break; + default : + $msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod', + array($call_method, $method)); + + throw new InvalidParameterException($msg); + } + + $API_METHODS[$method]["require_api_auth"] = $require_api_auth; + + $API_METHODS[$method]["require_user_auth"] = $require_user_auth; + + return true; +} + +/** + * 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; + + if (isset($API_METHODS[$method])) { + unset($API_METHODS[$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(elgg_echo('APIException:MethodCallNotImplemented', array($method))); + } + + // check API authentication if required + if ($API_METHODS[$method]["require_api_auth"] == true) { + $api_pam = new ElggPAM('api'); + if ($api_pam->authenticate() !== true) { + throw new APIException(elgg_echo('APIException:APIAuthenticationFailed')); + } + } + + $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 ($user_auth_result == false) { + throw new APIException($user_pam->getFailureMessage()); + } + } + + return true; +} + +/** + * Executes a 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])) { + $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"]))) { + + $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) { + $msg = elgg_echo('CallException:InvalidCallMethod', array($method, + $API_METHODS[$method]["call_method"])); + + throw new CallException($msg); + } + + $parameters = get_parameters_for_method($method); + + if (verify_parameters($method, $parameters) == false) { + // if verify_parameters fails, it throws exception which is not caught here + } + + $serialised_parameters = serialise_parameters($method, $parameters); + + // Execute function: Construct function and calling parameters + $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 + // If this function returns an api result itself, just return it + if ($result instanceof GenericResult) { + return $result; + } + + if ($result === false) { + $msg = elgg_echo('APIException:FunctionParseError', array($function, $serialised_parameters)); + throw new APIException($msg); + } + + if ($result === NULL) { + // If no value + $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. + return SuccessResult::getInstance($result); +} + +/** + * Get the request method. + * + * @return string HTTP request method + * @access private + */ +function get_call_method() { + return $_SERVER['REQUEST_METHOD']; +} + +/** + * This function analyses all expected parameters for a given method + * + * This function sanitizes the input parameters and returns them in + * 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; + + $sanitised = array(); + + // if there are parameters, sanitize them + 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 !== '' && $param !== null) { + $sanitised[$k] = $param; + } else { + // parameter wasn't passed so check for default + if (isset($v['default'])) { + $sanitised[$k] = $v['default']; + } + } + } + } + + return $sanitised; +} + +/** + * Get POST data + * Since this is called through a handler, we need to manually get the post data + * + * @return POST data as string encoded as multipart/form-data + * @access private + */ +function get_post_data() { + + $postdata = file_get_contents('php://input'); + + return $postdata; +} + +/** + * Verify that the required parameters are present + * + * @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; + + // are there any parameters for this method + if (!(isset($API_METHODS[$method]["parameters"]))) { + return true; // no so return + } + + // check that the parameters were registered correctly and all required ones are there + 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'])) { + + $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)) { + $msg = elgg_echo('APIException:MissingParameterInMethod', array($key, $method)); + throw new APIException($msg); + } + } + + return true; +} + +/** + * Serialize an array of parameters for an API method call + * + * @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; + + // are there any parameters for this method + if (!(isset($API_METHODS[$method]["parameters"]))) { + return ''; // if not, return + } + + $serialised_parameters = ""; + foreach ($API_METHODS[$method]['parameters'] as $key => $value) { + + // avoid warning on parameters that are not required and not present + if (!isset($parameters[$key])) { + continue; + } + + // Set variables casting to type. + switch (strtolower($value['type'])) + { + case 'int': + case 'integer' : + $serialised_parameters .= "," . (int)trim($parameters[$key]); + break; + case 'bool': + case 'boolean': + // change word false to boolean false + if (strcasecmp(trim($parameters[$key]), "false") == 0) { + $serialised_parameters .= ',false'; + } else if ($parameters[$key] == 0) { + $serialised_parameters .= ',false'; + } else { + $serialised_parameters .= ',true'; + } + + break; + case 'string': + $serialised_parameters .= ",'" . addcslashes(trim($parameters[$key]), "'") . "'"; + break; + case 'float': + $serialised_parameters .= "," . (float)trim($parameters[$key]); + break; + case 'array': + // we can handle an array of strings, maybe ints, definitely not booleans or other arrays + if (!is_array($parameters[$key])) { + $msg = elgg_echo('APIException:ParameterNotArray', array($key)); + throw new APIException($msg); + } + + $array = "array("; + + foreach ($parameters[$key] as $k => $v) { + $k = sanitise_string($k); + $v = sanitise_string($v); + + $array .= "'$k'=>'$v',"; + } + + $array = trim($array, ","); + + $array .= ")"; + $array = ",$array"; + + $serialised_parameters .= $array; + break; + default: + $msg = elgg_echo('APIException:UnrecognisedTypeCast', array($value['type'], $key, $method)); + throw new APIException($msg); + } + } + + return $serialised_parameters; +} + +// API authorization handlers ///////////////////////////////////////////////////////////////////// + +/** + * 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; + + // check that an API key is present + $api_key = get_input('api_key'); + if ($api_key == "") { + throw new APIException(elgg_echo('APIException:MissingAPIKey')); + } + + // check that it is active + $api_user = get_api_user($CONFIG->site_id, $api_key); + if (!$api_user) { + // key is not active or does not exist + throw new APIException(elgg_echo('APIException:BadAPIKey')); + } + + // can be used for keeping stats + // plugin can also return false to fail this authentication method + 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; + + // Get api header + $api_header = get_and_validate_api_headers(); + + // Pull API user details + $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); + } + + // Get the secret key + $secret_key = $api_user->secret; + + // get the query string + $query = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], '?') + 1); + + // calculate expected HMAC + $hmac = calculate_hmac( $api_header->hmac_algo, + $api_header->time, + $api_header->nonce, + $api_header->api_key, + $secret_key, + $query, + $api_header->method == 'POST' ? $api_header->posthash : ""); + + + if ($api_header->hmac !== $hmac) { + throw new SecurityException("HMAC is invalid. {$api_header->hmac} != [calc]$hmac"); + } + + // Now make sure this is not a replay + if (cache_hmac_check_replay($hmac)) { + throw new SecurityException(elgg_echo('SecurityException:DupePacket')); + } + + // Validate post data + 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) { + $msg = elgg_echo('SecurityException:InvalidPostHash', + array($calculated_posthash, $api_header->posthash)); + + throw new SecurityException($msg); + } + } + + return true; +} + +// HMAC ///////////////////////////////////////////////////////////////////// + +/** + * This function looks at the super-global variable $_SERVER and extracts the various + * header variables needed for the HMAC PAM + * + * @return stdClass Containing all the values. + * @throws APIException Detailing any error. + * @access private + */ +function get_and_validate_api_headers() { + $result = new stdClass; + + $result->method = get_call_method(); + // Only allow these methods + if (($result->method != "GET") && ($result->method != "POST")) { + throw new APIException(elgg_echo('APIException:NotGetOrPost')); + } + + $result->api_key = $_SERVER['HTTP_X_ELGG_APIKEY']; + if ($result->api_key == "") { + throw new APIException(elgg_echo('APIException:MissingAPIKey')); + } + + $result->hmac = $_SERVER['HTTP_X_ELGG_HMAC']; + if ($result->hmac == "") { + throw new APIException(elgg_echo('APIException:MissingHmac')); + } + + $result->hmac_algo = $_SERVER['HTTP_X_ELGG_HMAC_ALGO']; + if ($result->hmac_algo == "") { + throw new APIException(elgg_echo('APIException:MissingHmacAlgo')); + } + + $result->time = $_SERVER['HTTP_X_ELGG_TIME']; + if ($result->time == "") { + throw new APIException(elgg_echo('APIException:MissingTime')); + } + + // Must have been sent within 25 hour period. + // 25 hours is more than enough to handle server clock drift. + // 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))) { + throw new APIException(elgg_echo('APIException:TemporalDrift')); + } + + $result->nonce = $_SERVER['HTTP_X_ELGG_NONCE']; + if ($result->nonce == "") { + throw new APIException(elgg_echo('APIException:MissingNonce')); + } + + if ($result->method == "POST") { + $result->posthash = $_SERVER['HTTP_X_ELGG_POSTHASH']; + if ($result->posthash == "") { + throw new APIException(elgg_echo('APIException:MissingPOSTHash')); + } + + $result->posthash_algo = $_SERVER['HTTP_X_ELGG_POSTHASH_ALGO']; + if ($result->posthash_algo == "") { + throw new APIException(elgg_echo('APIException:MissingPOSTAlgo')); + } + + $result->content_type = $_SERVER['CONTENT_TYPE']; + if ($result->content_type == "") { + throw new APIException(elgg_echo('APIException:MissingContentType')); + } + } + + return $result; +} + +/** + * Map various algorithms to their PHP equivs. + * 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 + "sha" => "sha1", // alias for sha1 + "sha1" => "sha1", + "sha256" => "sha256" + ); + + if (array_key_exists($algo, $supported_algos)) { + return $supported_algos[$algo]; + } + + throw new APIException(elgg_echo('APIException:AlgorithmNotSupported', array($algo))); +} + +/** + * Calculate the HMAC for the http request. + * 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 $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 = "") { + + global $CONFIG; + + elgg_log("HMAC Parts: $algo, $time, $api_key, $secret_key, $get_variables, $post_hash"); + + $ctx = hash_init(map_api_hash($algo), HASH_HMAC, $secret_key); + + hash_update($ctx, trim($time)); + hash_update($ctx, trim($nonce)); + hash_update($ctx, trim($api_key)); + hash_update($ctx, trim($get_variables)); + if (trim($post_hash) != "") { + hash_update($ctx, trim($post_hash)); + } + + return urlencode(base64_encode(hash_final($ctx, true))); +} + +/** + * Calculate a hash for some post data. + * + * @todo Work out how to handle really large bits of data. + * + * @param string $postdata 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)); + + hash_update($ctx, $postdata); + + return hash_final($ctx); +} + +/** + * This function will do two things. Firstly it verifies that a HMAC signature + * 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 + // allowed in get_and_validate_headers + $cache = new ElggHMACCache(90000); + + if (!$cache->load($hmac)) { + $cache->save($hmac, $hmac); + + return false; + } + + return true; +} + +// API key functions ///////////////////////////////////////////////////////////////////// + +/** + * 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) { + global $CONFIG; + + if (!isset($site_guid)) { + $site_guid = $CONFIG->site_id; + } + + $site_guid = (int)$site_guid; + + $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 + ($site_guid, '$public', '$secret')"); + + if ($insert) { + return get_api_user($site_guid, $public); + } + + return false; +} + +/** + * 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 + * + * @return mixed stdClass representing the database row or false. + */ +function get_api_user($site_guid, $api_key) { + global $CONFIG; + + $api_key = sanitise_string($api_key); + $site_guid = (int)$site_guid; + + $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). + * + * @return bool + */ +function remove_api_user($site_guid, $api_key) { + global $CONFIG; + + $keypair = get_api_user($site_guid, $api_key); + if ($keypair) { + return delete_data("DELETE from {$CONFIG->dbprefix}api_users where id={$keypair->id}"); + } + + return false; +} + + +// User Authorization functions + +/** + * Check the user token + * This examines whether an authentication token is present and returns true if + * 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. + * + * @return bool + * @access private + */ +function pam_auth_usertoken() { + global $CONFIG; + + $token = get_input('auth_token'); + if (!$token) { + return false; + } + + $validated_userid = validate_user_token($token, $CONFIG->site_id); + + if ($validated_userid) { + $u = get_entity($validated_userid); + + // Could we get the user? + if (!$u) { + return false; + } + + // Not an elgg user + if ((!$u instanceof ElggUser)) { + return false; + } + + // User is banned + if ($u->isBanned()) { + return false; + } + + // Fail if we couldn't log the user in + if (!login($u)) { + return false; + } + + return true; + } + + return false; +} + +/** + * See if the user has a valid login sesson + * + * @return bool + * @access private + */ +function pam_auth_session() { + return elgg_is_logged_in(); +} + +// 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) + * + * @return bool + */ +function create_user_token($username, $expire = 60) { + global $CONFIG; + + $site_guid = $CONFIG->site_id; + $user = get_user_by_username($username); + $time = time(); + $time += 60 * $expire; + $token = md5(rand() . microtime() . $username . $time . $site_guid); + + if (!$user) { + return false; + } + + 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'")) { + return $token; + } + + return false; +} + +/** + * Get all tokens attached to a user + * + * @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; + + if (!isset($site_guid)) { + $site_guid = $CONFIG->site_id; + } + + $site_guid = (int)$site_guid; + $user_guid = (int)$user_guid; + + $tokens = get_data("SELECT * from {$CONFIG->dbprefix}users_apisessions + where user_guid=$user_guid and site_guid=$site_guid"); + + return $tokens; +} + +/** + * 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. + * + * @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) { + global $CONFIG; + + if (!isset($site_guid)) { + $site_guid = $CONFIG->site_id; + } + + $site_guid = (int)$site_guid; + $token = sanitise_string($token); + + $time = time(); + + $user = get_data_row("SELECT * from {$CONFIG->dbprefix}users_apisessions + where token='$token' and site_guid=$site_guid and $time < expires"); + + if ($user) { + return $user->user_guid; + } + + return false; +} + +/** + * Remove user token + * + * @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; + + if (!isset($site_guid)) { + $site_guid = $CONFIG->site_id; + } + + $site_guid = (int)$site_guid; + $token = sanitise_string($token); + + return delete_data("DELETE from {$CONFIG->dbprefix}users_apisessions + where site_guid=$site_guid and token='$token'"); +} + +/** + * Remove expired tokens + * + * @return bool + * @since 1.7.0 + */ +function remove_expired_user_tokens() { + global $CONFIG; + + $site_guid = $CONFIG->site_id; + + $time = time(); + + return delete_data("DELETE from {$CONFIG->dbprefix}users_apisessions + where site_guid=$site_guid and expires < $time"); +} + +// 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 = ""; + + foreach ($headers as $k => $v) { + $headers_str .= trim($k) . ": " . trim($v) . "\r\n"; + } + + return trim($headers_str); +} + +/** + * 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 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') { + + global $CONFIG; + + $headers = array(); + $encoded_params = array(); + + $method = strtoupper($method); + switch (strtoupper($method)) { + case 'GET' : + case 'POST' : + break; + default: + $msg = elgg_echo('NotImplementedException:CallMethodNotImplemented', array($method)); + throw new NotImplementedException($msg); + } + + // Time + $time = time(); + + // Nonce + $nonce = uniqid(''); + + // URL encode all the parameters + foreach ($call as $k => $v) { + $encoded_params[] = urlencode($k) . '=' . urlencode($v); + } + + $params = implode('&', $encoded_params); + + // Put together the query string + $url = $url . "?" . $params; + + // Construct headers + $posthash = ""; + if ($method == 'POST') { + $posthash = calculate_posthash($post_data, 'md5'); + } + + if ((isset($keys['public'])) && (isset($keys['private']))) { + $headers['X-Elgg-apikey'] = $keys['public']; + $headers['X-Elgg-time'] = $time; + $headers['X-Elgg-nonce'] = $nonce; + $headers['X-Elgg-hmac-algo'] = 'sha1'; + $headers['X-Elgg-hmac'] = calculate_hmac('sha1', + $time, + $nonce, + $keys['public'], + $keys['private'], + $params, + $posthash + ); + } + if ($method == 'POST') { + $headers['X-Elgg-posthash'] = $posthash; + $headers['X-Elgg-posthash-algo'] = 'md5'; + + $headers['Content-type'] = $content_type; + $headers['Content-Length'] = strlen($post_data); + } + + // Opt array + $http_opts = array( + 'method' => $method, + 'header' => serialise_api_headers($headers) + ); + if ($method == 'POST') { + $http_opts['content'] = $post_data; + } + + $opts = array('http' => $http_opts); + + // Send context + $context = stream_context_create($opts); + + // Send the query and get the result and decode. + elgg_log("APICALL: $url"); + $results = file_get_contents($url, false, $context); + + return $results; +} + +/** + * 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 + * + * @return string + */ +function send_api_get_call($url, array $call, array $keys) { + return send_api_call($keys, $url, $call); +} + +/** + * 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 $content_type The content type + * + * @return string + */ +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. + * + * @param string $secret_key Your secret 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 + +/** + * 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; + + // sort first + ksort($API_METHODS); + + return $API_METHODS; +} + +/** + * The auth.gettoken API. + * This API call lets a user log in, returning an authentication token which can be used + * to authenticate a user for a period of time. It is passed in future calls as the parameter + * auth_token. + * + * @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 (true === elgg_authenticate($username, $password)) { + $token = create_user_token($username); + if ($token) { + return $token; + } + } + + throw new SecurityException(elgg_echo('SecurityException:authenticationfailed')); +} + +// Error handler functions + +/** Define a global array of errors */ +$ERRORS = array(); + +/** + * API 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 + * + * @throws Exception + */ +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 . ")"; + + switch ($errno) { + case E_USER_ERROR: + error_log("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); + break; + + case E_WARNING : + case E_USER_WARNING : + error_log("WARNING: " . $error); + $ERRORS[] = "WARNING: " . $error; + break; + + default: + error_log("DEBUG: " . $error); + $ERRORS[] = "DEBUG: " . $error; + } +} + +/** + * API PHP Exception handler. + * This is a generic exception handler for PHP exceptions. This will catch any + * uncaught exception, end API execution and return the result to the requestor + * as an ErrorResult in the requested format. + * + * @param Exception $exception Exception + * + * @return void + * @access private + */ +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); + + echo elgg_view_page($exception->getMessage(), elgg_view("api/output", array("result" => $result))); +} + + +// Services handler + +/** + * Services handler - turns request over to the registered handler + * If no handler is found, this returns a 404 error + * + * @param string $handler Handler name + * @param array $request Request string + * + * @return void + * @access private + */ +function service_handler($handler, $request) { + global $CONFIG; + + elgg_set_context('api'); + + $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); + // Which view - xml, json, ... + if ($reponse_format) { + elgg_set_viewtype($reponse_format); + } else { + // default to xml + elgg_set_viewtype("xml"); + } + + if (!isset($CONFIG->servicehandler) || empty($handler)) { + // no handlers set or bad url + header("HTTP/1.0 404 Not Found"); + exit; + } else if (isset($CONFIG->servicehandler[$handler]) && is_callable($CONFIG->servicehandler[$handler])) { + $function = $CONFIG->servicehandler[$handler]; + call_user_func($function, $request, $handler); + } else { + // no handler for this web service + header("HTTP/1.0 404 Not Found"); + exit; + } +} + +/** + * Registers a web services handler + * + * @param string $handler Web services type + * @param string $function Your function name + * + * @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, true)) { + $CONFIG->servicehandler[$handler] = $function; + return true; + } + + return false; +} + +/** + * Remove a web service + * To replace a web service handler, register the desired handler over the old on + * 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, $CONFIG->servicehandler[$handler])) { + unset($CONFIG->servicehandler[$handler]); + } +} + +/** + * REST API handler + * + * @return void + * @access private + * + * @throws SecurityException|APIException + */ +function rest_handler() { + global $CONFIG; + + // 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))); +} + +// 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; +} + +/** + * 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'); + + 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); + + // 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 + ); +} + + +elgg_register_event_handler('init', 'system', 'api_init'); diff --git a/engine/lib/widgets.php b/engine/lib/widgets.php new file mode 100644 index 000000000..d73dd6330 --- /dev/null +++ b/engine/lib/widgets.php @@ -0,0 +1,419 @@ +<?php +/** + * Elgg widgets library. + * Contains code for handling widgets. + * + * @package Elgg.Core + * @subpackage Widgets + */ + +/** + * 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 + */ +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(); + } + + $sorted_widgets = array(); + foreach ($widgets as $widget) { + if (!isset($sorted_widgets[(int)$widget->column])) { + $sorted_widgets[(int)$widget->column] = array(); + } + $sorted_widgets[(int)$widget->column][$widget->order] = $widget; + } + + foreach ($sorted_widgets as $col => $widgets) { + ksort($sorted_widgets[$col]); + } + + return $sorted_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 + * + * @return int|false Widget GUID or false on failure + * @since 1.8.0 + */ +function elgg_create_widget($owner_guid, $handler, $context, $access_id = null) { + if (empty($owner_guid) || empty($handler) || !elgg_is_widget_type($handler)) { + return false; + } + + $owner = get_entity($owner_guid); + if (!$owner) { + return false; + } + + $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 (!$widget->save()) { + return false; + } + + // private settings cannot be set until ElggWidget saved + $widget->handler = $handler; + $widget->context = $context; + + return $widget->getGUID(); +} + +/** + * Can the user edit the widget layout + * + * 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 elgg_can_edit_widget_layout($context, $user_guid = 0) { + + $user = get_entity((int)$user_guid); + if (!$user) { + $user = elgg_get_logged_in_user_entity(); + } + + $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); +} + +/** + * Regsiter a widget type + * + * This should be called by plugins in their init function. + * + * @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 elgg_register_widget_type($handler, $name, $description, $context = "all", $multiple = false) { + + if (!$handler || !$name) { + return false; + } + + global $CONFIG; + + if (!isset($CONFIG->widgets)) { + $CONFIG->widgets = new stdClass; + } + if (!isset($CONFIG->widgets->handlers)) { + $CONFIG->widgets->handlers = array(); + } + + $handlerobj = new stdClass; + $handlerobj->name = $name; + $handlerobj->description = $description; + $handlerobj->context = explode(",", $context); + $handlerobj->multiple = $multiple; + + $CONFIG->widgets->handlers[$handler] = $handlerobj; + + return true; +} + +/** + * Remove a widget type + * + * @param string $handler The identifier for the widget + * + * @return void + * @since 1.8.0 + */ +function elgg_unregister_widget_type($handler) { + global $CONFIG; + + if (!isset($CONFIG->widgets)) { + return; + } + + if (!isset($CONFIG->widgets->handlers)) { + return; + } + + if (isset($CONFIG->widgets->handlers[$handler])) { + unset($CONFIG->widgets->handlers[$handler]); + } +} + +/** + * Has a widget type with the specified handler been registered + * + * @param string $handler The widget handler identifying string + * + * @return bool Whether or not that widget type exists + * @since 1.8.0 + */ +function elgg_is_widget_type($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; +} + +/** + * Get the widget types for a context + * + * 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; + + if (empty($CONFIG->widgets) || + empty($CONFIG->widgets->handlers) || + !is_array($CONFIG->widgets->handlers)) { + // no widgets + return array(); + } + + if (!$context) { + $context = elgg_get_context(); + } + + $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; + } + } + } + + return $widgets; +} + +/** + * Regsiter entity of object, widget as ElggWidget objects + * + * @return void + * @access private + */ +function elgg_widget_run_once() { + add_subtype("object", "widget", "ElggWidget"); +} + +/** + * Function to initialize widgets functionality + * + * @return void + * @access private + */ +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"); +} + +/** + * 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, + * + * '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 elgg_default_widgets_init() { + global $CONFIG; + $default_widgets = elgg_trigger_plugin_hook('get_list', 'default_widgets', null, array()); + + $CONFIG->default_widget_info = $default_widgets; + + if ($default_widgets) { + elgg_register_admin_menu_item('configure', 'default_widgets', 'appearance'); + + // 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'); + + // only register the callback once per event + $events = array(); + foreach ($default_widgets as $info) { + $events[$info['event'] . ',' . $info['entity_type']] = $info; + } + foreach ($events as $info) { + elgg_register_event_handler($info['event'], $info['entity_type'], 'elgg_create_default_widgets'); + } + } +} + +/** + * 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 object $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; + } + + $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); + + 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; + } + + $new_widget->save(); + } + + elgg_set_ignore_access($old_ia); + elgg_pop_context(); + } + } + } +} + +/** + * 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 elgg_default_widgets_permissions_override($hook, $type, $return, $params) { + if ($type == 'object' && $params['subtype'] == 'widget') { + return elgg_in_context('create_default_widgets') ? true : null; + } + + return null; +} + +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 new file mode 100644 index 000000000..bfe1a8645 --- /dev/null +++ b/engine/lib/xml-rpc.php @@ -0,0 +1,203 @@ +<?php +/** + * 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); + } + + 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]; + } + + 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 */ +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); + } + + // Result in right format, return it. + return $result; + } + + // 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 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 new file mode 100644 index 000000000..ff82d7e8a --- /dev/null +++ b/engine/lib/xml.php @@ -0,0 +1,111 @@ +<?php +/** + * 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>"; + } + + 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; +} + +/** + * 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 (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"; + } + + if ($n == 0) { + $output .= "</array>\n"; + } + + return $output; +} + +/** + * Parse an XML file into an object. + * + * @param string $xml The XML + * + * @return object + */ +function xml_to_object($xml) { + return new ElggXMLElement($xml); +} |