diff options
Diffstat (limited to 'engine/lib')
95 files changed, 36058 insertions, 0 deletions
diff --git a/engine/lib/access.php b/engine/lib/access.php new file mode 100644 index 000000000..de0693ea8 --- /dev/null +++ b/engine/lib/access.php @@ -0,0 +1,1078 @@ +<?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. + * + * @param string $hook + * @param string $type + * @param bool $value + * @param array $params + * @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. + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array + * + * @access private + */ +function access_test($hook, $type, $value, $params) { +	global $CONFIG; + +	$value[] = $CONFIG->path . 'engine/tests/api/access_collections.php'; +	return $value; +} + +// Tell the access functions the system has booted, plugins are loaded, +// and the user is logged in so it can start caching +elgg_register_event_handler('ready', 'system', 'access_init'); + +// For overrided permissions +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..8047914ac --- /dev/null +++ b/engine/lib/actions.php @@ -0,0 +1,549 @@ +<?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. +	// Logout for convenience. +	// file/download (see #2010) +	$exceptions = array( +		'admin/plugins/disable', +		'logout', +		'file/download', +	); + +	if (!in_array($action, $exceptions)) { +		action_gatekeeper($action); +	} + +	$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; +	} +} + +/** + * Is the token timestamp within acceptable range? + *  + * @param int $ts timestamp from the CSRF token + *  + * @return bool + */ +function _elgg_validate_token_timestamp($ts) { +	$action_token_timeout = elgg_get_config('action_token_timeout'); +	// default is 2 hours +	$timeout = ($action_token_timeout !== null) ? $action_token_timeout : 2; + +	$hour = 60 * 60; +	$timeout = $timeout * $hour; +	$now = time(); + +	// Validate time to ensure its not crazy +	return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout)); +} + +/** + * Validate an action token. + * + * 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) { +	if (!$token) { +		$token = get_input('__elgg_token'); +	} + +	if (!$ts) { +		$ts = get_input('__elgg_ts'); +	} + +	$session_id = session_id(); + +	if (($token) && ($ts) && ($session_id)) { +		// generate token, check with input and forward if invalid +		$required_token = generate_action_token($ts); + +		// Validate token +		if ($token == $required_token) { +			 +			if (_elgg_validate_token_timestamp($ts)) { +				// We have already got this far, so unless anything +				// else says something to the contrary 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) { +				// this is necessary because of #5133 +				if (elgg_is_xhr()) { +					register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); +				} else { +					register_error(elgg_echo('actiongatekeeper:timeerror')); +				} +			} +		} else if ($visibleerrors) { +			// this is necessary because of #5133 +			if (elgg_is_xhr()) { +				register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); +			} else { +				register_error(elgg_echo('actiongatekeeper:tokeninvalid')); +			} +		} +	} else { +		if (! empty($_SERVER['CONTENT_LENGTH']) && empty($_POST)) { +			// The size of $_POST or uploaded file has exceed the size limit +			$error_msg = elgg_trigger_plugin_hook('action_gatekeeper:upload_exceeded_msg', 'all', array( +				'post_size' => $_SERVER['CONTENT_LENGTH'], +				'visible_errors' => $visibleerrors, +			), elgg_echo('actiongatekeeper:uploadexceeded')); +		} else { +			$error_msg = elgg_echo('actiongatekeeper:missingfields'); +		} +		if ($visibleerrors) { +			register_error($error_msg); +		} +	} + +	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. + * + * @param string $action The action being performed + *  + * @return mixed True if valid or redirects. + * @access private + */ +function action_gatekeeper($action) { +	if ($action === 'login') { +		if (validate_action_token(false)) { +			return true; +		} +		 +		$token = get_input('__elgg_token'); +		$ts = (int)get_input('__elgg_ts'); +		if ($token && _elgg_validate_token_timestamp($ts)) { +			// The tokens are present and the time looks valid: this is probably a mismatch due to the  +			// login form being on a different domain. +			register_error(elgg_echo('actiongatekeeper:crosssitelogin')); +			 +			 +			forward('login', 'csrf'); +		} +		 +		// let the validator send an appropriate msg +		validate_action_token(); +		 +	} elseif (validate_action_token()) { +		return true; +	} + +	forward(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 (32 bytes: "z" to indicate format + 186-bit key in Base64 URL). + * + * Used during installation and saves as a datalist. + * + * Note: Old secrets were hex encoded. + * + * @return mixed The site secret hash or false + * @access private + * @todo Move to better file. + */ +function init_site_secret() { +	$secret = 'z' . ElggCrypto::getRandomString(31); + +	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; +} + +/** + * Get the strength of the site secret + * + * @return string "strong", "moderate", or "weak" + * @access private + */ +function _elgg_get_site_secret_strength() { +	$secret = get_site_secret(); +	if ($secret[0] !== 'z') { +		$rand_max = getrandmax(); +		if ($rand_max < pow(2, 16)) { +			return 'weak'; +		} +		if ($rand_max < pow(2, 32)) { +			return 'moderate'; +		} +	} +	return 'strong'; +} + +/** + * Check if an action is registered and its script exists. + * + * @param string $action Action name + * + * @return bool + * @since 1.8.0 + */ +function elgg_action_exists($action) { +	global $CONFIG; + +	return (isset($CONFIG->actions[$action]) && file_exists($CONFIG->actions[$action]['file'])); +} + +/** + * Checks whether the request was requested via ajax + * + * @return bool whether page was requested via ajax + * @since 1.8.0 + */ +function elgg_is_xhr() { +	return isset($_SERVER['HTTP_X_REQUESTED_WITH']) +		&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest' || +		get_input('X-Requested-With') === 'XMLHttpRequest'; +} + +/** + * Catch calls to forward() in ajax request and force an exit. + * + * Forces response is json of the following form: + * <pre> + * { + *     "current_url": "the.url.we/were/coming/from", + *     "forward_url": "the.url.we/were/going/to", + *     "system_messages": { + *         "messages": ["msg1", "msg2", ...], + *         "errors": ["err1", "err2", ...] + *     }, + *     "status": -1 //or 0 for success if there are no error messages present + * } + * </pre> + * where "system_messages" is all message registers at the point of forwarding + * + * @param string $hook + * @param string $type + * @param string $reason + * @param array $params + * @return void + * @access private + */ +function ajax_forward_hook($hook, $type, $reason, $params) { +	if (elgg_is_xhr()) { +		// always pass the full structure to avoid boilerplate JS code. +		$params = array( +			'output' => '', +			'status' => 0, +			'system_messages' => array( +				'error' => array(), +				'success' => array() +			) +		); + +		//grab any data echo'd in the action +		$output = ob_get_clean(); + +		//Avoid double-encoding in case data is json +		$json = json_decode($output); +		if (isset($json)) { +			$params['output'] = $json; +		} else { +			$params['output'] = $output; +		} + +		//Grab any system messages so we can inject them via ajax too +		$system_messages = system_messages(NULL, ""); + +		if (isset($system_messages['success'])) { +			$params['system_messages']['success'] = $system_messages['success']; +		} + +		if (isset($system_messages['error'])) { +			$params['system_messages']['error'] = $system_messages['error']; +			$params['status'] = -1; +		} + +		// Check the requester can accept JSON responses, if not fall back to +		// returning JSON in a plain-text response.  Some libraries request +		// JSON in an invisible iframe which they then read from the iframe, +		// however some browsers will not accept the JSON MIME type. +		if (stripos($_SERVER['HTTP_ACCEPT'], 'application/json') === FALSE) { +			header("Content-type: text/plain"); +		} else { +			header("Content-type: application/json"); +		} + +		echo json_encode($params); +		exit; +	} +} + +/** + * Buffer all output echo'd directly in the action for inclusion in the returned JSON. + * @return void + * @access private + */ +function ajax_action_hook() { +	if (elgg_is_xhr()) { +		ob_start(); +	} +} + +/** + * Initialize some ajaxy actions features + * @access private + */ +function actions_init() { +	elgg_register_action('security/refreshtoken', '', 'public'); + +	elgg_register_simplecache_view('js/languages/en'); + +	elgg_register_plugin_hook_handler('action', 'all', 'ajax_action_hook'); +	elgg_register_plugin_hook_handler('forward', 'all', 'ajax_forward_hook'); +} + +elgg_register_event_handler('init', 'system', 'actions_init'); diff --git a/engine/lib/admin.php b/engine/lib/admin.php new file mode 100644 index 000000000..f36f29668 --- /dev/null +++ b/engine/lib/admin.php @@ -0,0 +1,663 @@ +<?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; +} + +/** + * Get admin notices. An admin must be logged in since the notices are private. + * + * @param int $limit Limit + * + * @return array Array of admin notices + * @since 1.8.0 + */ +function elgg_get_admin_notices($limit = 10) { +	return elgg_get_entities_from_metadata(array( +		'type' => 'object', +		'subtype' => 'admin_notice', +		'limit' => $limit +	)); +} + +/** + * Check if an admin notice is currently active. + * + * @param string $id The unique ID used to register the notice. + * + * @return bool + * @since 1.8.0 + */ +function elgg_admin_notice_exists($id) { +	$old_ia = elgg_set_ignore_access(true); +	$notice = elgg_get_entities_from_metadata(array( +		'type' => 'object', +		'subtype' => 'admin_notice', +		'metadata_name_value_pair' => array('name' => 'admin_notice_id', 'value' => $id) +	)); +	elgg_set_ignore_access($old_ia); + +	return ($notice) ? TRUE : FALSE; +} + +/** + * Add an admin area section or child section. + * This is a wrapper for elgg_register_menu_item(). + * + * Used in conjuction with http://elgg.org/admin/section_id/child_section style + * page handler. See the documentation at the top of this file for more details + * on that. + * + * The text of the menu item is obtained from elgg_echo(admin:$parent_id:$menu_id) + * + * This function handles registering the parent if it has not been registered. + * + * @param string $section   The menu section to add to + * @param string $menu_id   The unique ID of section + * @param string $parent_id If a child section, the parent section id + * @param int    $priority  The menu item priority + * + * @return bool + * @since 1.8.0 + */ +function elgg_register_admin_menu_item($section, $menu_id, $parent_id = NULL, $priority = 100) { + +	// make sure parent is registered +	if ($parent_id && !elgg_is_menu_item_registered('page', $parent_id)) { +		elgg_register_admin_menu_item($section, $parent_id); +	} + +	// in the admin section parents never have links +	if ($parent_id) { +		$href = "admin/$parent_id/$menu_id"; +	} else { +		$href = NULL; +	} + +	$name = $menu_id; +	if ($parent_id) { +		$name = "$parent_id:$name"; +	} + +	return elgg_register_menu_item('page', array( +		'name' => $name, +		'href' => $href, +		'text' => elgg_echo("admin:$name"), +		'context' => 'admin', +		'parent_name' => $parent_id, +		'priority' => $priority, +		'section' => $section +	)); +} + +/** + * Initialize the admin backend. + * @return void + * @access private + */ +function admin_init() { +	elgg_register_action('admin/user/ban', '', 'admin'); +	elgg_register_action('admin/user/unban', '', 'admin'); +	elgg_register_action('admin/user/delete', '', 'admin'); +	elgg_register_action('admin/user/resetpassword', '', 'admin'); +	elgg_register_action('admin/user/makeadmin', '', 'admin'); +	elgg_register_action('admin/user/removeadmin', '', 'admin'); + +	elgg_register_action('admin/site/update_basic', '', 'admin'); +	elgg_register_action('admin/site/update_advanced', '', 'admin'); +	elgg_register_action('admin/site/flush_cache', '', 'admin'); +	elgg_register_action('admin/site/unlock_upgrade', '', 'admin'); +	elgg_register_action('admin/site/regenerate_secret', '', 'admin'); + +	elgg_register_action('admin/menu/save', '', 'admin'); + +	elgg_register_action('admin/delete_admin_notice', '', 'admin'); + +	elgg_register_action('profile/fields/reset', '', 'admin'); +	elgg_register_action('profile/fields/add', '', 'admin'); +	elgg_register_action('profile/fields/edit', '', 'admin'); +	elgg_register_action('profile/fields/delete', '', 'admin'); +	elgg_register_action('profile/fields/reorder', '', 'admin'); + +	elgg_register_simplecache_view('css/admin'); +	elgg_register_simplecache_view('js/admin'); +	$url = elgg_get_simplecache_url('js', 'admin'); +	elgg_register_js('elgg.admin', $url); +	elgg_register_js('jquery.jeditable', 'vendors/jquery/jquery.jeditable.mini.js'); + +	// administer +	// dashboard +	elgg_register_menu_item('page', array( +		'name' => 'dashboard', +		'href' => 'admin/dashboard', +		'text' => elgg_echo('admin:dashboard'), +		'context' => 'admin', +		'priority' => 10, +		'section' => 'administer' +	)); +	// statistics +	elgg_register_admin_menu_item('administer', 'statistics', null, 20); +	elgg_register_admin_menu_item('administer', 'overview', 'statistics'); +	elgg_register_admin_menu_item('administer', 'server', 'statistics'); + +	// users +	elgg_register_admin_menu_item('administer', 'users', null, 20); +	elgg_register_admin_menu_item('administer', 'online', 'users', 10); +	elgg_register_admin_menu_item('administer', 'admins', 'users', 20); +	elgg_register_admin_menu_item('administer', 'newest', 'users', 30); +	elgg_register_admin_menu_item('administer', 'add', 'users', 40); + +	// configure +	// plugins +	elgg_register_menu_item('page', array( +		'name' => 'plugins', +		'href' => 'admin/plugins', +		'text' => elgg_echo('admin:plugins'), +		'context' => 'admin', +		'priority' => 75, +		'section' => 'configure' +	)); + +	// settings +	elgg_register_admin_menu_item('configure', 'appearance', null, 50); +	elgg_register_admin_menu_item('configure', 'settings', null, 100); +	elgg_register_admin_menu_item('configure', 'basic', 'settings', 10); +	elgg_register_admin_menu_item('configure', 'advanced', 'settings', 20); +	elgg_register_admin_menu_item('configure', 'advanced/site_secret', 'settings', 25); +	elgg_register_admin_menu_item('configure', 'menu_items', 'appearance', 30); +	elgg_register_admin_menu_item('configure', 'profile_fields', 'appearance', 40); +	// default widgets is added via an event handler elgg_default_widgets_init() in widgets.php +	// because it requires additional setup. + +	// plugin settings are added in elgg_admin_add_plugin_settings_menu() via the admin page handler +	// for performance reasons. + +	// we want plugin settings menu items to be sorted alphabetical +	if (elgg_in_context('admin')) { +		elgg_register_plugin_hook_handler('prepare', 'menu:page', 'elgg_admin_sort_page_menu'); +	} + +	if (elgg_is_admin_logged_in()) { +		elgg_register_menu_item('topbar', array( +			'name' => 'administration', +			'href' => 'admin', +			'text' => elgg_view_icon('settings') . elgg_echo('admin'), +			'priority' => 100, +			'section' => 'alt', +		)); +	} +			 +	// widgets +	$widgets = array('online_users', 'new_users', 'content_stats', 'admin_welcome', 'control_panel'); +	foreach ($widgets as $widget) { +		elgg_register_widget_type( +				$widget, +				elgg_echo("admin:widget:$widget"), +				elgg_echo("admin:widget:$widget:help"), +				'admin' +		); +	} + +	// automatic adding of widgets for admin +	elgg_register_event_handler('make_admin', 'user', 'elgg_add_admin_widgets'); + +	elgg_register_page_handler('admin', 'admin_page_handler'); +	elgg_register_page_handler('admin_plugin_screenshot', 'admin_plugin_screenshot_page_handler'); +	elgg_register_page_handler('admin_plugin_text_file', 'admin_markdown_page_handler'); +} + +/** + * 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; +	} + +	foreach ($active_plugins as $plugin) { +		$plugin_id = $plugin->getID(); +		$settings_view_old = 'settings/' . $plugin_id . '/edit'; +		$settings_view_new = 'plugins/' . $plugin_id . '/settings'; +		if (elgg_view_exists($settings_view_new) || elgg_view_exists($settings_view_old)) { +			elgg_register_menu_item('page', array( +				'name' => $plugin_id, +				'href' => "admin/plugin_settings/$plugin_id", +				'text' => $plugin->getManifest()->getName(), +				'parent_name' => 'settings', +				'context' => 'admin', +				'section' => 'configure', +			)); +		} +	} +} + +/** + * Sort the plugin settings menu items + * + * @param string $hook + * @param string $type + * @param array  $return + * @param array  $params + * + * @return void + * @since 1.8.0 + * @access private + */ +function elgg_admin_sort_page_menu($hook, $type, $return, $params) { +	$configure_items = $return['configure']; +	/* @var ElggMenuItem[] $configure_items */ +	foreach ($configure_items as $menu_item) { +		if ($menu_item->getName() == 'settings') { +			$settings = $menu_item; +		} +	} + +	// keep the basic and advanced settings at the top +	/* @var ElggMenuItem $settings */ +	$children = $settings->getChildren(); +	$site_settings = array_splice($children, 0, 2); +	usort($children, array('ElggMenuBuilder', 'compareByText')); +	array_splice($children, 0, 0, $site_settings); +	$settings->setChildren($children); +} + +/** + * Handles any set up required for administration pages + * + * @return void + * @access private + */ +function admin_pagesetup() { +	if (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') { +		if (isset($page[1]) && (elgg_view_exists("settings/{$page[1]}/edit") ||  +			elgg_view_exists("plugins/{$page[1]}/settings"))) { + +			$view = 'admin/plugin_settings'; +			$plugin = elgg_get_plugin_from_id($page[1]); +			$vars['plugin'] = $plugin; + +			$title = elgg_echo("admin:{$page[0]}"); +		} else { +			forward('', '404'); +		} +	} else { +		$view = 'admin/' . implode('/', $page); +		$title = elgg_echo("admin:{$page[0]}"); +		if (count($page) > 1) { +			$title .= ' : ' . elgg_echo('admin:' .  implode(':', $page)); +		} +	} + +	// 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 array $pages + * @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. + * + * @param string $event + * @param string $type + * @param ElggUser $user + * + * @return null|true + * @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); +				/* @var ElggWidget $widget */ +				$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..5e9b530de --- /dev/null +++ b/engine/lib/annotations.php @@ -0,0 +1,618 @@ +<?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)) { +		// @todo should throw in this case? +		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 ElggAnnotation|false + */ +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 ElggAnnotation[]|mixed + * @since 1.8.0 + */ +function elgg_get_annotations(array $options = array()) { + +	// @todo remove support for count shortcut - see #4393 +	if (isset($options['__egefac']) && $options['__egefac']) { +		unset($options['__egefac']); +	} else { +		// support shortcut of 'count' => true for 'annotation_calculation' => 'count' +		if (isset($options['count']) && $options['count']) { +			$options['annotation_calculation'] = 'count'; +			unset($options['count']); +		}		 +	} +	 +	$options['metastring_type'] = 'annotations'; +	return elgg_get_metastring_based_objects($options); +} + +/** + * Deletes annotations based on $options. + * + * @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 bool|null true on success, false on failure, null if no annotations to delete. + * @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 bool|null true on success, false on failure, null if no annotations disabled. + * @since 1.8.0 + */ +function elgg_disable_annotations(array $options) { +	if (!elgg_is_valid_options_for_batch_operation($options, 'annotations')) { +		return false; +	} +	 +	// if we can see hidden (disabled) we need to use the offset +	// otherwise we risk an infinite loop if there are more than 50 +	$inc_offset = access_get_show_hidden_status(); + +	$options['metastring_type'] = 'annotations'; +	return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset); +} + +/** + * Enables annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * + * @warning In order to enable annotations, you must first use + * {@link access_show_hidden_entities()}. + * + * @param array $options An options array. {@See elgg_get_annotations()} + * @return bool|null true on success, false on failure, null if no metadata enabled. + * @since 1.8.0 + */ +function 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'; + +	// see #4393 +	// @todo remove after the 'count' shortcut is removed from elgg_get_annotations() +	$options['__egefac'] = true; + +	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) { +	$defaults = array( +		'calculation' => 'sum', +		'order_by' => 'annotation_calculation desc' +	); +	$options = array_merge($defaults, $options); + +	return elgg_list_entities($options, 'elgg_get_entities_from_annotation_calculation'); +} + +/** + * Export the annotations for the specified entity + * + * @param string $hook        'export' + * @param string $type        'all' + * @param mixed  $returnvalue Default return value + * @param mixed  $params      Parameters determining what annotations to export + * + * @elgg_plugin_hook export all + * + * @return array + * @throws InvalidParameterException + * @access private + */ +function export_annotation_plugin_hook($hook, $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']; +	$options = array('guid' => $guid, 'limit' => 0); +	if (isset($params['name'])) { +		$options['annotation_name'] = $params['name']; +	} + +	$result = elgg_get_annotations($options); + +	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 = sanitize_int($entity_guid); +	$owner_guid = sanitize_int($owner_guid); +	$annotation_type = sanitize_string($annotation_type); + +	$sql = "SELECT a.id FROM {$CONFIG->dbprefix}annotations a" . +			" JOIN {$CONFIG->dbprefix}metastrings m ON a.name_id = m.id" . +			" WHERE a.owner_guid = $owner_guid AND a.entity_guid = $entity_guid" . +			" AND m.string = '$annotation_type'"; + +	if (get_data_row($sql)) { +		return TRUE; +	} + +	return FALSE; +} + +/** + * Return the URL for a comment + * + * @param ElggAnnotation $comment The comment object  + * @return string + * @access private + */ +function elgg_comment_url_handler(ElggAnnotation $comment) { +	$entity = $comment->getEntity(); +	if ($entity) { +		return $entity->getURL() . '#item-annotation-' . $comment->id; +	} +	return ""; +} + +/** + * Register an annotation url handler. + * + * @param string $extender_name The name, default 'all'. + * @param string $function_name The function. + * + * @return string + */ +function elgg_register_annotation_url_handler($extender_name = "all", $function_name) { +	return elgg_register_extender_url_handler('annotation', $extender_name, $function_name); +} + +/** + * Register annotation unit tests + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array + * @access private + */ +function 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..3116c1a9b --- /dev/null +++ b/engine/lib/cache.php @@ -0,0 +1,453 @@ +<?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() { +	elgg_reset_system_cache(); +} +/** + * @access private + */ +function elgg_filepath_cache_save($type, $data) { +	return elgg_save_system_cache($type, $data); +} +/** + * @access private + */ +function elgg_filepath_cache_load($type) { +	return elgg_load_system_cache($type); +} +/** + * @access private + */ +function elgg_enable_filepath_cache() { +	elgg_enable_system_cache(); +} +/** + * @access private + */ +function elgg_disable_filepath_cache() { +	elgg_disable_system_cache(); +} + +/* 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(); +	elgg_register_simplecache_view("$type/$view");// see #5302 +	if (elgg_is_simplecache_enabled()) { +		$url = elgg_get_site_url() . "cache/$type/$viewtype/$view.$lastcache.$type"; +	} else { +		$url = elgg_get_site_url() . "$type/$view.$lastcache.$type"; +		$elements = array("view" => $viewtype); +		$url = elgg_http_add_url_query_elements($url, $elements); +	} +	 +	return $url; +} + +/** + * Regenerates the simple cache. + * + * @warning This does not invalidate the cache, but actively rebuilds it. + * + * @param string $viewtype Optional viewtype to regenerate. Defaults to all valid viewtypes. + * + * @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.lang", 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..e6f95934c --- /dev/null +++ b/engine/lib/calendar.php @@ -0,0 +1,573 @@ +<?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. + * + * @todo this function also accepts an array(type => subtypes) for 3rd arg. Should we document this? + * + * @param int     $start_time     The start time as a unix timestamp. + * @param int     $end_time       The end time as a unix timestamp. + * @param string  $type           The type of entity (eg "user", "object" etc) + * @param string  $subtype        The arbitrary subtype of the entity + * @param int     $owner_guid     The GUID of the owning user + * @param string  $order_by       The field to order by; by default, time_created desc + * @param int     $limit          The number of entities to return; 10 by default + * @param int     $offset         The indexing offset, 0 by default + * @param boolean $count          Set to true to get a count instead of entities. Defaults to false. + * @param int     $site_guid      Site to get entities for. Default 0 = current site. -1 = any. + * @param mixed   $container_guid Container or containers to get entities from (default: any). + * + * @return array|false + * @access private + */ +function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "asc", $limit = 10, $offset = 0, $count = false, $site_guid = 0, +$container_guid = null) { +	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..55e5bbd36 --- /dev/null +++ b/engine/lib/configuration.php @@ -0,0 +1,632 @@ +<?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; +	} +	/* @var ElggSite $site */ + +	return $site->url; +} + +/** + * Get the plugin path for this installation + * + * @return string + * @since 1.8.0 + */ +function elgg_get_plugins_path() { +	global $CONFIG; +	return $CONFIG->pluginspath; +} + +/** + * Get the data directory path for this installation + * + * @return string + * @since 1.8.0 + */ +function elgg_get_data_path() { +	global $CONFIG; +	return $CONFIG->dataroot; +} + +/** + * Get the root directory path for this installation + * + * @return string + * @since 1.8.0 + */ +function elgg_get_root_path() { +	global $CONFIG; +	return $CONFIG->path; +} + +/** + * Get an Elgg configuration value + * + * @param string $name      Name of the configuration value + * @param int    $site_guid NULL for installation setting, 0 for default site + * + * @return mixed Configuration value or null if it does not exist + * @since 1.8.0 + */ +function elgg_get_config($name, $site_guid = 0) { +	global $CONFIG; + +	$name = trim($name); + +	if (isset($CONFIG->$name)) { +		return $CONFIG->$name; +	} + +	if ($site_guid === null) { +		// installation wide setting +		$value = datalist_get($name); +	} else { +		// hit DB only if we're not sure if value exists or not +		if (!isset($CONFIG->site_config_loaded)) { +			// site specific setting +			if ($site_guid == 0) { +				$site_guid = (int) $CONFIG->site_id; +			} +			$value = get_config($name, $site_guid); +		} else { +			$value = null; +		} +	} + +	// @todo document why we don't cache false +	if ($value === false) { +		return null; +	} + +	$CONFIG->$name = $value; +	return $value; +} + +/** + * Set an Elgg configuration value + * + * @warning This does not persist the configuration setting. Use elgg_save_config() + * + * @param string $name  Name of the configuration value + * @param mixed  $value Value + * + * @return void + * @since 1.8.0 + */ +function elgg_set_config($name, $value) { +	global $CONFIG; + +	$name = trim($name); + +	$CONFIG->$name = $value; +} + +/** + * Save a configuration setting + * + * @param string $name      Configuration name (cannot be greater than 255 characters) + * @param mixed  $value     Configuration value. Should be string for installation setting + * @param int    $site_guid NULL for installation setting, 0 for default site + * + * @return bool + * @since 1.8.0 + */ +function elgg_save_config($name, $value, $site_guid = 0) { +	global $CONFIG; + +	$name = trim($name); + +	if (strlen($name) > 255) { +		elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); +		return false; +	} + +	elgg_set_config($name, $value); + +	if ($site_guid === NULL) { +		if (is_array($value) || is_object($value)) { +			return false; +		} +		return datalist_set($name, $value); +	} else { +		if ($site_guid == 0) { +			$site_guid = (int) $CONFIG->site_id; +		} +		return set_config($name, $value, $site_guid); +	} +} + +/** + * Check that installation has completed and the database is populated. + * + * @throws InstallationException|DatabaseException + * @return void + * @access private + */ +function verify_installation() { +	global $CONFIG; + +	if (isset($CONFIG->installed)) { +		return; +	} + +	try { +		$dblink = get_db_link('read'); +		if (!$dblink) { +			throw new DatabaseException(); +		} + +		mysql_query("SELECT value FROM {$CONFIG->dbprefix}datalists WHERE name = 'installed'", $dblink); +		if (mysql_errno($dblink) > 0) { +			throw new DatabaseException(); +		} + +		$CONFIG->installed = true; + +	} catch (DatabaseException $e) { +		throw new InstallationException(elgg_echo('InstallationException:SiteNotInstalled')); +	} +} + +/** + * An array of key value pairs from the datalists table. + * + * Used as a cache in datalist functions. + * + * @global array $DATALIST_CACHE + */ +$DATALIST_CACHE = array(); + +/** + * Get the value of a datalist element. + * + * @internal Datalists are stored in the datalist table. + * + * @tip Use datalists to store information common to a full installation. + * + * @param string $name The name of the datalist + * @return string|null|false String if value exists, null if doesn't, false on error + * @access private + */ +function datalist_get($name) { +	global $CONFIG, $DATALIST_CACHE; + +	$name = trim($name); + +	// cannot store anything longer than 255 characters in db, so catch here +	if (elgg_strlen($name) > 255) { +		elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); +		return false; +	} + +	$name = sanitise_string($name); +	if (isset($DATALIST_CACHE[$name])) { +		return $DATALIST_CACHE[$name]; +	} + +	// If memcache enabled then cache value in memcache +	$value = null; +	static $datalist_memcache; +	if ((!$datalist_memcache) && (is_memcache_available())) { +		$datalist_memcache = new ElggMemcache('datalist_memcache'); +	} +	if ($datalist_memcache) { +		$value = $datalist_memcache->load($name); +	} +	if ($value) { +		return $value; +	} + +	// [Marcus Povey 20090217 : Now retrieving all datalist values on first +	// load as this saves about 9 queries per page] +	// This also causes OOM problems when the datalists table is large +	// @todo make a list of datalists that we want to get in one grab +	$result = get_data("SELECT * from {$CONFIG->dbprefix}datalists"); +	if ($result) { +		foreach ($result as $row) { +			$DATALIST_CACHE[$row->name] = $row->value; + +			// Cache it if memcache is available +			if ($datalist_memcache) { +				$datalist_memcache->save($row->name, $row->value); +			} +		} + +		if (isset($DATALIST_CACHE[$name])) { +			return $DATALIST_CACHE[$name]; +		} +	} + +	return null; +} + +/** + * Set the value for a datalist element. + * + * @param string $name  The name of the datalist + * @param string $value The new value + * + * @return bool + * @access private + */ +function datalist_set($name, $value) { +	global $CONFIG, $DATALIST_CACHE; + +	// cannot store anything longer than 255 characters in db, so catch before we set +	if (elgg_strlen($name) > 255) { +		elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); +		return false; +	} + +	$sanitised_name = sanitise_string($name); +	$sanitised_value = sanitise_string($value); + +	// If memcache is available then invalidate the cached copy +	static $datalist_memcache; +	if ((!$datalist_memcache) && (is_memcache_available())) { +		$datalist_memcache = new ElggMemcache('datalist_memcache'); +	} + +	if ($datalist_memcache) { +		$datalist_memcache->delete($name); +	} + +	$success = insert_data("INSERT into {$CONFIG->dbprefix}datalists" +		. " set name = '{$sanitised_name}', value = '{$sanitised_value}'" +		. " ON DUPLICATE KEY UPDATE value='{$sanitised_value}'"); + +	if ($success !== FALSE) { +		$DATALIST_CACHE[$name] = $value; +		return true; +	} else { +		return false; +	} +} + +/** + * Run a function one time per installation. + * + * If you pass a timestamp as the second argument, it will run the function + * only if (i) it has never been run before or (ii) the timestamp is >= + * the last time it was run. + * + * @warning Functions are determined by their name.  If you change the name of a function + * it will be run again. + * + * @tip Use $timelastupdatedcheck in your plugins init function to perform automated + * upgrades.  Schedule a function to run once and pass the timestamp of the new release. + * This will cause the run once function to be run on all installations.  To perform + * additional upgrades, create new functions for each release. + * + * @warning The function name cannot be longer than 255 characters long due to + * the current schema for the datalist table. + * + * @internal A datalist entry $functioname is created with the value of time(). + * + * @param string $functionname         The name of the function you want to run. + * @param int    $timelastupdatedcheck A UNIX timestamp. If time() is > than this, + *                                     this function will be run again. + * + * @return bool + */ +function run_function_once($functionname, $timelastupdatedcheck = 0) { +	$lastupdated = datalist_get($functionname); +	if ($lastupdated) { +		$lastupdated = (int) $lastupdated; +	} elseif ($lastupdated !== false) { +		$lastupdated = 0; +	} else { +		// unable to check datalist +		return false; +	} +	if (is_callable($functionname) && $lastupdated <= $timelastupdatedcheck) { +		$functionname(); +		datalist_set($functionname, time()); +		return true; +	} else { +		return false; +	} +} + +/** + * 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 bool + * @todo The config table doens't have numeric primary keys so insert_data returns 0. + * @todo Use "INSERT ... ON DUPLICATE KEY UPDATE" instead of trying to delete then add. + * @see unset_config() + * @see get_config() + * @access private + */ +function set_config($name, $value, $site_guid = 0) { +	global $CONFIG; + +	$name = trim($name); + +	// cannot store anything longer than 255 characters in db, so catch before we set +	if (elgg_strlen($name) > 255) { +		elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); +		return false; +	} + +	// Unset existing +	unset_config($name, $site_guid); + +	$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..4f3d05b93 --- /dev/null +++ b/engine/lib/cron.php @@ -0,0 +1,89 @@ +<?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 + * @throws CronException + * @access private + */ +function cron_page_handler($page) { +	if (!isset($page[0])) { +		forward(); +	} + +	$period = strtolower($page[0]); + +	$allowed_periods = array( +		'minute', 'fiveminute', 'fifteenmin', 'halfhour', 'hourly', +		'daily', 'weekly', 'monthly', 'yearly', 'reboot' +	); + +	if (!in_array($period, $allowed_periods)) { +		throw new CronException(elgg_echo('CronException:unknownperiod', array($period))); +	} + +	// Get a list of parameters +	$params = array(); +	$params['time'] = time(); + +	// Data to return to +	$old_stdout = ""; +	ob_start(); + +	$old_stdout = elgg_trigger_plugin_hook('cron', $period, $params, $old_stdout); +	$std_out = ob_get_clean(); + +	echo $std_out . $old_stdout; +	return true; +} + +/** + * Register cron's pages as public in case we're in Walled Garden mode + * + * @param string $hook         public_pages + * @param string $type         system + * @param array  $return_value Array of pages to allow + * @param mixed  $params       Params + * + * @return array + * @access private + */ +function cron_public_pages($hook, $type, $return_value, $params) { +	$return_value[] = 'cron/minute'; +	$return_value[] = 'cron/fiveminute'; +	$return_value[] = 'cron/fifteenmin'; +	$return_value[] = 'cron/halfhour'; +	$return_value[] = 'cron/hourly'; +	$return_value[] = 'cron/daily'; +	$return_value[] = 'cron/weekly'; +	$return_value[] = 'cron/monthly'; +	$return_value[] = 'cron/yearly'; +	$return_value[] = 'cron/reboot'; + +	return $return_value; +} + +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..a7949788d --- /dev/null +++ b/engine/lib/database.php @@ -0,0 +1,764 @@ +<?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 cache as: + * <code> + * $DB_QUERY_CACHE[query hash] => array(result1, result2, ... resultN) + * </code> + * @see elgg_query_runner() for details on the hash. + * + * @warning Elgg used to set this as an empty array to turn off the cache + * + * @global ElggLRUCache|null $DB_QUERY_CACHE + * @access private + */ +global $DB_QUERY_CACHE; +$DB_QUERY_CACHE = null; + +/** + * Queries to be executed upon shutdown. + * + * These queries are saved to an array and executed using + * a function registered by register_shutdown_function(). + * + * Queries are saved as an array in the format: + * <code> + * $DB_DELAYED_QUERIES[] = array( + * 	'q' => str $query, + * 	'l' => resource $dblink, + * 	'h' => str $handler // a callback function + * ); + * </code> + * + * @global array $DB_DELAYED_QUERIES + * @access private + */ +global $DB_DELAYED_QUERIES; +$DB_DELAYED_QUERIES = array(); + +/** + * Database connection resources. + * + * Each database link created with establish_db_link($name) is stored in + * $dblink as $dblink[$name] => resource.  Use get_db_link($name) to retrieve it. + * + * @global resource[] $dblink + * @access private + */ +global $dblink; +$dblink = array(); + +/** + * Database call count + * + * Each call to the database increments this counter. + * + * @global integer $dbcalls + * @access private + */ +global $dbcalls; +$dbcalls = 0; + +/** + * Establish a connection to the database servser + * + * Connect to the database server and use the Elgg database for a particular database link + * + * @param string $dblinkname The type of database connection. Used to identify the + * resource. eg "read", "write", or "readwrite". + * + * @return void + * @throws DatabaseException + * @access private + */ +function establish_db_link($dblinkname = "readwrite") { +	// Get configuration, and globalise database link +	global $CONFIG, $dblink, $DB_QUERY_CACHE; + +	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)) { +		// @todo if we keep this cache in 1.9, expose the size as a config parameter +		$DB_QUERY_CACHE = new ElggLRUCache(200); +	} +} + +/** + * 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; + +	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 resource 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 string $query The query to explain + * @param mixed $link  The database link resource to user. + * + * @return mixed An object of the query's result, or FALSE + * @access private + */ +function explain_query($query, $link) { +	if ($result = execute_query("explain " . $query, $link)) { +		return mysql_fetch_object($result); +	} + +	return FALSE; +} + +/** + * 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 resource   $dblink The DB link + * + * @return resource result of mysql_query() + * @throws DatabaseException + * @access private + */ +function execute_query($query, $dblink) { +	global $dbcalls; + +	if ($query == NULL) { +		throw new DatabaseException(elgg_echo('DatabaseException:InvalidQuery')); +	} + +	if (!is_resource($dblink)) { +		throw new DatabaseException(elgg_echo('DatabaseException:InvalidDBLink')); +	} + +	$dbcalls++; + +	$result = mysql_query($query, $dblink); + +	if (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|string $dblink  The database link to use or the link type (read | write) + * @param string   $handler A callback function to pass the results array to + * + * @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 $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. +	// https://github.com/elgg/elgg/issues/4049 +	$hash = (string)$callback . (int)$single . $query; + +	// Is cached? +	if ($DB_QUERY_CACHE) { +		if (isset($DB_QUERY_CACHE[$hash])) { +			elgg_log("DB query $query results returned from cache (hash: $hash)", 'NOTICE'); +			return $DB_QUERY_CACHE[$hash]; +		} +	} + +	$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 iteration. +		$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) { + +	elgg_log("DB query $query", 'NOTICE'); +	 +	$dblink = get_db_link('write'); + +	_elgg_invalidate_query_cache(); + +	if (execute_query("$query", $dblink)) { +		return mysql_insert_id($dblink); +	} + +	return FALSE; +} + +/** + * Update 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) { + +	elgg_log("DB query $query", 'NOTICE'); + +	$dblink = get_db_link('write'); + +	_elgg_invalidate_query_cache(); + +	if (execute_query("$query", $dblink)) { +		return TRUE; +	} + +	return FALSE; +} + +/** + * Remove data 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) { + +	elgg_log("DB query $query", 'NOTICE'); + +	$dblink = get_db_link('write'); + +	_elgg_invalidate_query_cache(); + +	if (execute_query("$query", $dblink)) { +		return mysql_affected_rows($dblink); +	} + +	return FALSE; +} + +/** + * Invalidate the query cache + * + * @access private + */ +function _elgg_invalidate_query_cache() { +	global $DB_QUERY_CACHE; +	if ($DB_QUERY_CACHE instanceof ElggLRUCache) { +		$DB_QUERY_CACHE->clear(); +		elgg_log("Query cache invalidated", 'NOTICE'); +	} elseif ($DB_QUERY_CACHE) { +		// In case someone sets the cache to an array and primes it with data +		$DB_QUERY_CACHE = array(); +		elgg_log("Query cache invalidated", 'NOTICE'); +	} +} + +/** + * 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 { +					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..ee95b5611 --- /dev/null +++ b/engine/lib/deprecated-1.7.php @@ -0,0 +1,1164 @@ +<?php +/** + * Get entities with the specified access collection id. + * + * @deprecated 1.7. Use elgg_get_entities_from_access_id() + * + * @param int    $collection_id  ID of collection + * @param string $entity_type    Type of entities + * @param string $entity_subtype Subtype of entities + * @param int    $owner_guid     Guid of owner + * @param int    $limit          Limit of number of entities to return + * @param int    $offset         Skip this many entities + * @param string $order_by       Column to order by + * @param int    $site_guid      The site guid + * @param bool   $count          Return a count or entities + * + * @return array + */ +function get_entities_from_access_id($collection_id, $entity_type = "", $entity_subtype = "", +	$owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { +	// log deprecated warning +	elgg_deprecated_notice('get_entities_from_access_id() was deprecated by elgg_get_entities()', 1.7); + +	if (!$collection_id) { +		return FALSE; +	} + +	// build the options using given parameters +	$options = array(); +	$options['limit'] = $limit; +	$options['offset'] = $offset; +	$options['count'] = $count; + +	if ($entity_type) { +		$options['type'] = sanitise_string($entity_type); +	} + +	if ($entity_subtype) { +		$options['subtype'] = $entity_subtype; +	} + +	if ($site_guid) { +		$options['site_guid'] = $site_guid; +	} + +	if ($order_by) { +		$options['order_by'] = sanitise_string("e.time_created, $order_by"); +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	if ($site_guid) { +		$options['site_guid'] = $site_guid; +	} + +	$options['access_id'] = $collection_id; + +	return elgg_get_entities_from_access_id($options); +} + +/** + * @deprecated 1.7 + */ +function get_entities_from_access_collection($collection_id, $entity_type = "", $entity_subtype = "", +	$owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { + +	elgg_deprecated_notice('get_entities_from_access_collection() was deprecated by elgg_get_entities()', 1.7); + +	return get_entities_from_access_id($collection_id, $entity_type, $entity_subtype, +			$owner_guid, $limit, $offset, $order_by, $site_guid, $count); +} + +/** + * Get entities from annotations + * + * No longer used. + * + * @deprecated 1.7 Use elgg_get_entities_from_annotations() + * + * @param mixed  $entity_type    Type of entity + * @param mixed  $entity_subtype Subtype of entity + * @param string $name           Name of annotation + * @param string $value          Value of annotation + * @param int    $owner_guid     Guid of owner of annotation + * @param int    $group_guid     Guid of group + * @param int    $limit          Limit + * @param int    $offset         Offset + * @param string $order_by       SQL order by string + * @param bool   $count          Count or return entities + * @param int    $timelower      Lower time limit + * @param int    $timeupper      Upper time limit + * + * @return unknown_type + */ +function get_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", +$value = "", $owner_guid = 0, $group_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", +$count = false, $timelower = 0, $timeupper = 0) { +	$msg = 'get_entities_from_annotations() is deprecated by elgg_get_entities_from_annotations().'; +	elgg_deprecated_notice($msg, 1.7); + +	$options = array(); + +	if ($entity_type) { +		$options['types'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtypes'] = $entity_subtype; +	} + +	$options['annotation_names'] = $name; + +	if ($value) { +		$options['annotation_values'] = $value; +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['annotation_owner_guids'] = $owner_guid; +		} else { +			$options['annotation_owner_guid'] = $owner_guid; +		} +	} + +	if ($group_guid) { +		$options['container_guid'] = $group_guid; +	} + +	if ($limit) { +		$options['limit'] = $limit; +	} + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($order_by) { +		$options['order_by'] = "maxtime $order_by"; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	if ($timelower) { +		$options['annotation_created_time_lower'] = $timelower; +	} + +	if ($timeupper) { +		$options['annotation_created_time_upper'] = $timeupper; +	} + +	return elgg_get_entities_from_annotations($options); +} + +/** + * Lists entities + * + * @see elgg_view_entity_list + * + * @param string  $entity_type    Type of entity. + * @param string  $entity_subtype Subtype of entity. + * @param string  $name           Name of annotation. + * @param string  $value          Value of annotation. + * @param int     $limit          Maximum number of results to return. + * @param int     $owner_guid     Owner. + * @param int     $group_guid     Group container. Currently only supported if entity_type is object + * @param boolean $asc            Whether to list in ascending or descending order (default: desc) + * @param boolean $fullview       Whether to display the entities in full + * @param boolean $listtypetoggle Can 'gallery' view can be displayed (default: no) + * + * @deprecated 1.7 Use elgg_list_entities_from_annotations() + * @return string Formatted entity list + */ +function list_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", +$value = "", $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, +$listtypetoggle = false) { + +	$msg = 'list_entities_from_annotations is deprecated by elgg_list_entities_from_annotations'; +	elgg_deprecated_notice($msg, 1.8); + +	$options = array(); + +	if ($entity_type) { +		$options['types'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtypes'] = $entity_subtype; +	} + +	if ($name) { +		$options['annotation_names'] = $name; +	} + +	if ($value) { +		$options['annotation_values'] = $value; +	} + +	if ($limit) { +		$options['limit'] = $limit; +	} + +	if ($owner_guid) { +		$options['annotation_owner_guid'] = $owner_guid; +	} + +	if ($group_guid) { +		$options['container_guid'] = $group_guid; +	} + +	if ($asc) { +		$options['order_by'] = 'maxtime desc'; +	} + +	if ($offset = sanitise_int(get_input('offset', null))) { +		$options['offset'] = $offset; +	} + +	$options['full_view'] = $fullview; +	$options['list_type_toggle'] = $listtypetoggle; +	$options['pagination'] = $pagination; + +	return elgg_list_entities_from_annotations($options); +} + +/** + * Returns all php files in a directory. + * + * @deprecated 1.7 Use elgg_get_file_list() instead + * + * @param string $directory  Directory to look in + * @param array  $exceptions Array of extensions (with .!) to ignore + * @param array  $list       A list files to include in the return + * + * @return array + */ +function get_library_files($directory, $exceptions = array(), $list = array()) { +	elgg_deprecated_notice('get_library_files() deprecated by elgg_get_file_list()', 1.7); +	return elgg_get_file_list($directory, $exceptions, $list, array('.php')); +} + +/** + * Add action tokens to URL. + * + * @param string $url URL + * + * @return string + * + * @deprecated 1.7 final + */ +function elgg_validate_action_url($url) { +	elgg_deprecated_notice('elgg_validate_action_url() deprecated by elgg_add_action_tokens_to_url().', +		1.7); + +	return elgg_add_action_tokens_to_url($url); +} + +/** + * Does nothing. + * + * @deprecated 1.7 + * @return 0 + */ +function test_ip() { +	elgg_deprecated_notice('test_ip() was removed because of licensing issues.', 1.7); + +	return 0; +} + +/** + * Does nothing. + * + * @return bool + * @deprecated 1.7 + */ +function is_ip_in_array() { +	elgg_deprecated_notice('is_ip_in_array() was removed because of licensing issues.', 1.7); + +	return false; +} + +/** + * Returns entities. + * + * @deprecated 1.7.  Use elgg_get_entities(). + * + * @param string $type           Entity type + * @param string $subtype        Entity subtype + * @param int    $owner_guid     Owner GUID + * @param string $order_by       Order by clause + * @param int    $limit          Limit + * @param int    $offset         Offset + * @param bool   $count          Return a count or an array of entities + * @param int    $site_guid      Site GUID + * @param int    $container_guid Container GUID + * @param int    $timelower      Lower time limit + * @param int    $timeupper      Upper time limit + * + * @return array + */ +function get_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, +$offset = 0, $count = false, $site_guid = 0, $container_guid = null, $timelower = 0, +$timeupper = 0) { + +	elgg_deprecated_notice('get_entities() was deprecated by elgg_get_entities().', 1.7); + +	// rewrite owner_guid to container_guid to emulate old functionality +	if ($owner_guid != "") { +		if (is_null($container_guid)) { +			$container_guid = $owner_guid; +			$owner_guid = NULL; +		} +	} + +	$options = array(); +	if ($type) { +		if (is_array($type)) { +			$options['types'] = $type; +		} else { +			$options['type'] = $type; +		} +	} + +	if ($subtype) { +		if (is_array($subtype)) { +			$options['subtypes'] = $subtype; +		} else { +			$options['subtype'] = $subtype; +		} +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	if ($order_by) { +		$options['order_by'] = $order_by; +	} + +	// need to pass 0 for all option +	$options['limit'] = $limit; + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	if ($site_guid) { +		$options['site_guids'] = $site_guid; +	} + +	if ($container_guid) { +		$options['container_guids'] = $container_guid; +	} + +	if ($timeupper) { +		$options['created_time_upper'] = $timeupper; +	} + +	if ($timelower) { +		$options['created_time_lower'] = $timelower; +	} + +	$r = elgg_get_entities($options); +	return $r; +} + +/** + * Delete multiple entities that match a given query. + * This function iterates through and calls delete_entity on + * each one, this is somewhat inefficient but lets + * the 'delete' event be called for each entity. + * + * @deprecated 1.7. This is a dangerous function as it defaults to deleting everything. + * + * @param string $type       The type of entity (eg "user", "object" etc) + * @param string $subtype    The arbitrary subtype of the entity + * @param int    $owner_guid The GUID of the owning user + * + * @return false + */ +function delete_entities($type = "", $subtype = "", $owner_guid = 0) { +	elgg_deprecated_notice('delete_entities() was deprecated because no one should use it.', 1.7); +	return false; +} + +/** + * Lists entities. + * + * @param int  $owner_guid     Owner GUID + * @param int  $limit          Limit + * @param bool $fullview       Show entity full views + * @param bool $listtypetoggle Show list type toggle + * @param bool $allowedtypes   A string of the allowed types + * + * @return string + * @deprecated 1.7.  Use elgg_list_registered_entities(). + */ +function list_registered_entities($owner_guid = 0, $limit = 10, $fullview = true, +$listtypetoggle = false, $allowedtypes = true) { + +	elgg_deprecated_notice('list_registered_entities() was deprecated by elgg_list_registered_entities().', 1.7); + +	$options = array(); + +	// don't want to send anything if not being used. +	if ($owner_guid) { +		$options['owner_guid'] = $owner_guid; +	} + +	if ($limit) { +		$options['limit'] = $limit; +	} + +	if ($allowedtypes) { +		$options['allowed_types'] = $allowedtypes; +	} + +	// need to send because might be BOOL +	$options['full_view'] = $fullview; +	$options['list_type_toggle'] = $listtypetoggle; + +	$options['offset'] = get_input('offset', 0); + +	return elgg_list_registered_entities($options); +} + +/** + * Lists entities + * + * @deprecated 1.7.  Use elgg_list_entities(). + * + * @param string $type           Entity type + * @param string $subtype        Entity subtype + * @param int    $owner_guid     Owner GUID + * @param int    $limit          Limit + * @param bool   $fullview       Display entity full views? + * @param bool   $listtypetoggle Allow switching to gallery mode? + * @param bool   $pagination     Show pagination? + * + * @return string + */ +function list_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, +$listtypetoggle = false, $pagination = true) { + +	elgg_deprecated_notice('list_entities() was deprecated by elgg_list_entities()!', 1.7); + +	$options = array(); + +	// rewrite owner_guid to container_guid to emulate old functionality +	if ($owner_guid) { +		$options['container_guids'] = $owner_guid; +	} + +	if ($type) { +		$options['types'] = $type; +	} + +	if ($subtype) { +		$options['subtypes'] = $subtype; +	} + +	if ($limit) { +		$options['limit'] = $limit; +	} + +	if ($offset = sanitise_int(get_input('offset', null))) { +		$options['offset'] = $offset; +	} + +	$options['full_view'] = $fullview; +	$options['list_type_toggle'] = $listtypetoggle; +	$options['pagination'] = $pagination; + +	return elgg_list_entities($options); +} + +/** + * Searches for a group based on a complete or partial name or description + * + * @param string  $criteria The partial or full name or description + * @param int     $limit    Limit of the search. + * @param int     $offset   Offset. + * @param string  $order_by The order. + * @param boolean $count    Whether to return the count of results or just the results. + * + * @return mixed + * @deprecated 1.7 + */ +function search_for_group($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { +	elgg_deprecated_notice('search_for_group() was deprecated by new search plugin.', 1.7); +	global $CONFIG; + +	$criteria = sanitise_string($criteria); +	$limit = (int)$limit; +	$offset = (int)$offset; +	$order_by = sanitise_string($order_by); + +	$access = get_access_sql_suffix("e"); + +	if ($order_by == "") { +		$order_by = "e.time_created desc"; +	} + +	if ($count) { +		$query = "SELECT count(e.guid) as total "; +	} else { +		$query = "SELECT e.* "; +	} +	$query .= "from {$CONFIG->dbprefix}entities e" +		. " JOIN {$CONFIG->dbprefix}groups_entity g on e.guid=g.guid where "; + +	$query .= "(g.name like \"%{$criteria}%\" or g.description like \"%{$criteria}%\")"; +	$query .= " and $access"; + +	if (!$count) { +		$query .= " order by $order_by limit $offset, $limit"; // Add order and limit +		return get_data($query, "entity_row_to_elggstar"); +	} else { +		if ($count = get_data_row($query)) { +			return $count->total; +		} +	} +	return false; +} + +/** + * Returns a formatted list of groups suitable for injecting into search. + * + * @deprecated 1.7 + * + * @param string $hook        Hook name + * @param string $user        User + * @param mixed  $returnvalue Previous hook's return value + * @param string $tag         Tag to search on + * + * @return string + */ +function search_list_groups_by_name($hook, $user, $returnvalue, $tag) { +	elgg_deprecated_notice('search_list_groups_by_name() was deprecated by new search plugin', 1.7); +	// Change this to set the number of groups that display on the search page +	$threshold = 4; + +	$object = get_input('object'); + +	if (!get_input('offset') && (empty($object) || $object == 'group')) { +		if ($groups = search_for_group($tag, $threshold)) { +			$countgroups = search_for_group($tag, 0, 0, "", true); + +			$return = elgg_view('group/search/startblurb', array('count' => $countgroups, 'tag' => $tag)); +			foreach ($groups as $group) { +				$return .= elgg_view_entity($group); +			} +			$vars = array('count' => $countgroups, 'threshold' => $threshold, 'tag' => $tag); +			$return .= elgg_view('group/search/finishblurb', $vars); +			return $return; +		} +	} +} + +/** + * Displays a list of group objects that have been searched for. + * + * @see elgg_view_entity_list + * + * @param string $tag   Search criteria + * @param int    $limit The number of entities to display on a page + * + * @return string The list in a form suitable to display + * @deprecated 1.7 + */ +function list_group_search($tag, $limit = 10) { +	elgg_deprecated_notice('list_group_search() was deprecated by new search plugin.', 1.7); +	$offset = (int) get_input('offset'); +	$limit = (int) $limit; +	$count = (int) search_for_group($tag, 10, 0, '', true); +	$entities = search_for_group($tag, $limit, $offset); + +	return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); + +} + +/** + * Return a list of entities based on the given search criteria. + * + * @deprecated 1.7 use elgg_get_entities_from_metadata(). + * + * @param mixed  $meta_name      Metadat name + * @param mixed  $meta_value     Metadata value + * @param string $entity_type    The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int    $owner_guid     Owner GUID + * @param int    $limit          Limit + * @param int    $offset         Offset + * @param string $order_by       Optional ordering. + * @param int    $site_guid      Site GUID. 0 for current, -1 for any. + * @param bool   $count          Return a count instead of entities + * @param bool   $case_sensitive Metadata names case sensitivity + * + * @return int|array A list of entities, or a count if $count is set to true + */ +function get_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", +$entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", +$site_guid = 0, $count = FALSE, $case_sensitive = TRUE) { + +	elgg_deprecated_notice('get_entities_from_metadata() was deprecated by elgg_get_entities_from_metadata()!', 1.7); + +	$options = array(); + +	$options['metadata_names'] = $meta_name; + +	if ($meta_value) { +		$options['metadata_values'] = $meta_value; +	} + +	if ($entity_type) { +		$options['types'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtypes'] = $entity_subtype; +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	if ($limit) { +		$options['limit'] = $limit; +	} + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($order_by) { +		$options['order_by']; +	} + +	if ($site_guid) { +		$options['site_guid']; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	// need to be able to pass false +	$options['metadata_case_sensitive'] = $case_sensitive; + +	return elgg_get_entities_from_metadata($options); +} + +/** + * Return entities from metadata + * + * @deprecated 1.7.  Use elgg_get_entities_from_metadata(). + * + * @param mixed  $meta_array          Metadata name + * @param string $entity_type         The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype      The subtype of the entity. + * @param int    $owner_guid          Owner GUID + * @param int    $limit               Limit + * @param int    $offset              Offset + * @param string $order_by            Optional ordering. + * @param int    $site_guid           Site GUID. 0 for current, -1 for any. + * @param bool   $count               Return a count instead of entities + * @param bool   $meta_array_operator Operator for metadata values + * + * @return int|array A list of entities, or a count if $count is set to true + */ +function get_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", +$owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, +$count = false, $meta_array_operator = 'and') { + +	elgg_deprecated_notice('get_entities_from_metadata_multi() was deprecated by elgg_get_entities_from_metadata()!', 1.7); + +	if (!is_array($meta_array) || sizeof($meta_array) == 0) { +		return false; +	} + +	$options = array(); + +	$options['metadata_name_value_pairs'] = $meta_array; + +	if ($entity_type) { +		$options['types'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtypes'] = $entity_subtype; +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	if ($limit) { +		$options['limit'] = $limit; +	} + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($order_by) { +		$options['order_by']; +	} + +	if ($site_guid) { +		$options['site_guid']; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	$options['metadata_name_value_pairs_operator'] = $meta_array_operator; + +	return elgg_get_entities_from_metadata($options); +} + +/** + * Returns a menu item for use in the children section of add_menu() + * This is not currently used in the Elgg core. + * + * @param string $menu_name The name of the menu item + * @param string $menu_url  Its URL + * + * @return stdClass|false Depending on success + * @deprecated 1.7 + */ +function menu_item($menu_name, $menu_url) { +	elgg_deprecated_notice('menu_item() is deprecated by add_submenu_item', 1.7); +	return make_register_object($menu_name, $menu_url); +} + +/** + * Searches for an object based on a complete or partial title + * or description using full text searching. + * + * IMPORTANT NOTE: With MySQL's default setup: + * 1) $criteria must be 4 or more characters long + * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! + * + * @param string  $criteria The partial or full name or username. + * @param int     $limit    Limit of the search. + * @param int     $offset   Offset. + * @param string  $order_by The order. + * @param boolean $count    Whether to return the count of results or just the results. + * + * @return int|false + * @deprecated 1.7 + */ +function search_for_object($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { +	elgg_deprecated_notice('search_for_object() was deprecated by new search plugin.', 1.7); +	global $CONFIG; + +	$criteria = sanitise_string($criteria); +	$limit = (int)$limit; +	$offset = (int)$offset; +	$order_by = sanitise_string($order_by); +	$container_guid = (int)$container_guid; + +	$access = get_access_sql_suffix("e"); + +	if ($order_by == "") { +		$order_by = "e.time_created desc"; +	} + +	if ($count) { +		$query = "SELECT count(e.guid) as total "; +	} else { +		$query = "SELECT e.* "; +	} +	$query .= "from {$CONFIG->dbprefix}entities e +		join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid +		where match(o.title,o.description) against ('$criteria') and $access"; + +	if (!$count) { +		$query .= " order by $order_by limit $offset, $limit"; // Add order and limit +		return get_data($query, "entity_row_to_elggstar"); +	} else { +		if ($count = get_data_row($query)) { +			return $count->total; +		} +	} +	return false; +} + +/** + * Returns a formatted list of objects suitable for injecting into search. + * + * @deprecated 1.7 + * + * @param sting  $hook        Hook + * @param string $user        user + * @param mixed  $returnvalue Previous return value + * @param mixed  $tag         Search term + * + * @return array + */ +function search_list_objects_by_name($hook, $user, $returnvalue, $tag) { +	elgg_deprecated_notice('search_list_objects_by_name was deprecated by new search plugin.', 1.7); + +	// Change this to set the number of users that display on the search page +	$threshold = 4; + +	$object = get_input('object'); + +	if (!get_input('offset') && (empty($object) || $object == 'user')) { +		if ($users = search_for_user($tag, $threshold)) { +			$countusers = search_for_user($tag, 0, 0, "", true); + +			$return = elgg_view('user/search/startblurb', array('count' => $countusers, 'tag' => $tag)); +			foreach ($users as $user) { +				$return .= elgg_view_entity($user); +			} +			$return .= elgg_view('user/search/finishblurb', +				array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag)); + +			return $return; + +		} +	} +} + +/** + * Return entities from relationships + * + * @deprecated 1.7 Use elgg_get_entities_from_relationship() + * + * @param string $relationship         The relationship type + * @param int    $relationship_guid    The GUID of the relationship owner + * @param bool   $inverse_relationship Invert relationship? + * @param string $type                 Entity type + * @param string $subtype              Entity subtype + * @param int    $owner_guid           Entity owner GUID + * @param string $order_by             Order by clause + * @param int    $limit                Limit + * @param int    $offset               Offset + * @param bool   $count                Return a count instead of entities? + * @param int    $site_guid            Site GUID + * + * @return mixed + */ +function get_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + +	elgg_deprecated_notice('get_entities_from_relationship() was deprecated by elgg_get_entities_from_relationship()!', 1.7); + +	$options = array(); + +	$options['relationship'] = $relationship; +	$options['relationship_guid'] = $relationship_guid; +	$options['inverse_relationship'] = $inverse_relationship; + +	if ($type) { +		$options['types'] = $type; +	} + +	if ($subtype) { +		$options['subtypes'] = $subtype; +	} + +	if ($owner_guid) { +		$options['owner_guid'] = $owner_guid; +	} + +	$options['limit'] = $limit; + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($order_by) { +		$options['order_by']; +	} + +	if ($site_guid) { +		$options['site_guid']; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	return elgg_get_entities_from_relationship($options); +} + +/** + * Searches for a site based on a complete or partial name + * or description or url using full text searching. + * + * IMPORTANT NOTE: With MySQL's default setup: + * 1) $criteria must be 4 or more characters long + * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! + * + * @param string  $criteria The partial or full name or username. + * @param int     $limit    Limit of the search. + * @param int     $offset   Offset. + * @param string  $order_by The order. + * @param boolean $count    Whether to return the count of results or just the results. + * + * @return mixed + * @deprecated 1.7 + */ +function search_for_site($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { +	elgg_deprecated_notice('search_for_site() was deprecated by new search plugin.', 1.7); +	global $CONFIG; + +	$criteria = sanitise_string($criteria); +	$limit = (int)$limit; +	$offset = (int)$offset; +	$order_by = sanitise_string($order_by); + +	$access = get_access_sql_suffix("e"); + +	if ($order_by == "") { +		$order_by = "e.time_created desc"; +	} + +	if ($count) { +		$query = "SELECT count(e.guid) as total "; +	} else { +		$query = "SELECT e.* "; +	} +	$query .= "from {$CONFIG->dbprefix}entities e +		join {$CONFIG->dbprefix}sites_entity s on e.guid=s.guid +		where match(s.name, s.description, s.url) against ('$criteria') and $access"; + +	if (!$count) { +		$query .= " order by $order_by limit $offset, $limit"; // Add order and limit +		return get_data($query, "entity_row_to_elggstar"); +	} else { +		if ($count = get_data_row($query)) { +			return $count->total; +		} +	} +	return false; +} + +/** + * Searches for a user based on a complete or partial name or username. + * + * @param string  $criteria The partial or full name or username. + * @param int     $limit    Limit of the search. + * @param int     $offset   Offset. + * @param string  $order_by The order. + * @param boolean $count    Whether to return the count of results or just the results. + * + * @return mixed + * @deprecated 1.7 + */ +function search_for_user($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { +	elgg_deprecated_notice('search_for_user() was deprecated by new search.', 1.7); +	global $CONFIG; + +	$criteria = sanitise_string($criteria); +	$limit = (int)$limit; +	$offset = (int)$offset; +	$order_by = sanitise_string($order_by); + +	$access = get_access_sql_suffix("e"); + +	if ($order_by == "") { +		$order_by = "e.time_created desc"; +	} + +	if ($count) { +		$query = "SELECT count(e.guid) as total "; +	} else { +		$query = "SELECT e.* "; +	} +	$query .= "from {$CONFIG->dbprefix}entities e +		join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid where "; + +	$query .= "(u.name like \"%{$criteria}%\" or u.username like \"%{$criteria}%\")"; +	$query .= " and $access"; + +	if (!$count) { +		$query .= " order by $order_by limit $offset, $limit"; +		return get_data($query, "entity_row_to_elggstar"); +	} else { +		if ($count = get_data_row($query)) { +			return $count->total; +		} +	} +	return false; +} + +/** + * Displays a list of user objects that have been searched for. + * + * @see elgg_view_entity_list + * + * @param string $tag   Search criteria + * @param int    $limit The number of entities to display on a page + * + * @return string The list in a form suitable to display + * + * @deprecated 1.7 + */ +function list_user_search($tag, $limit = 10) { +	elgg_deprecated_notice('list_user_search() deprecated by new search', 1.7); +	$offset = (int) get_input('offset'); +	$limit = (int) $limit; +	$count = (int) search_for_user($tag, 10, 0, '', true); +	$entities = search_for_user($tag, $limit, $offset); + +	return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); +} + +/** + * Returns a formatted list of users suitable for injecting into search. + * + * @deprecated 1.7 + * + * @param string $hook        Hook name + * @param string $user        User? + * @param mixed  $returnvalue Previous hook's return value + * @param mixed  $tag         Tag to search against + * + * @return void + */ +function search_list_users_by_name($hook, $user, $returnvalue, $tag) { +	elgg_deprecated_notice('search_list_users_by_name() was deprecated by new search', 1.7); +	// Change this to set the number of users that display on the search page +	$threshold = 4; + +	$object = get_input('object'); + +	if (!get_input('offset') && (empty($object) || $object == 'user')) { +		if ($users = search_for_user($tag, $threshold)) { +			$countusers = search_for_user($tag, 0, 0, "", true); + +			$return = elgg_view('user/search/startblurb', array('count' => $countusers, 'tag' => $tag)); +			foreach ($users as $user) { +				$return .= elgg_view_entity($user); +			} + +			$vars = array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag); +			$return .= elgg_view('user/search/finishblurb', $vars); +			return $return; + +		} +	} +} + +/** + * Extend a view + * + * @deprecated 1.7.  Use elgg_extend_view(). + * + * @param string $view      The view to extend. + * @param string $view_name This view is added to $view + * @param int    $priority  The priority, from 0 to 1000, + *                          to add at (lowest numbers displayed first) + * @param string $viewtype  Not used + * + * @return void + */ +function extend_view($view, $view_name, $priority = 501, $viewtype = '') { +	elgg_deprecated_notice('extend_view() was deprecated by elgg_extend_view()!', 1.7); +	elgg_extend_view($view, $view_name, $priority, $viewtype); +} + +/** + * Get views in a dir + * + * @deprecated 1.7.  Use elgg_get_views(). + * + * @param string $dir  Dir + * @param string $base Base view + * + * @return array + */ +function get_views($dir, $base) { +	elgg_deprecated_notice('get_views() was deprecated by elgg_get_views()!', 1.7); +	elgg_get_views($dir, $base); +} + +/** + * Constructs and returns a register object. + * + * @param string $register_name  The name of the register + * @param mixed  $register_value The value of the register + * @param array  $children_array Optionally, an array of children + * + * @return false|stdClass Depending on success + * @deprecated 1.7 Use {@link add_submenu_item()} + */ +function make_register_object($register_name, $register_value, $children_array = array()) { +	elgg_deprecated_notice('make_register_object() is deprecated by add_submenu_item()', 1.7); +	if (empty($register_name) || empty($register_value)) { +		return false; +	} + +	$register = new stdClass; +	$register->name = $register_name; +	$register->value = $register_value; +	$register->children = $children_array; + +	return $register; +} + +/** + * THIS FUNCTION IS DEPRECATED. + * + * Delete a object's extra data. + * + * @todo - this should be removed - was deprecated in 1.5 or earlier + * + * @param int $guid GUID + * + * @return 1 + * @deprecated 1.7 + */ +function delete_object_entity($guid) { +	system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); + +	return 1; // Always return that we have deleted one row in order to not break existing code. +} + +/** + * THIS FUNCTION IS DEPRECATED. + * + * Delete a user's extra data. + * + * @todo remove + * + * @param int $guid User GUID + * + * @return 1 + * @deprecated 1.7 + */ +function delete_user_entity($guid) { +	system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); + +	return 1; // Always return that we have deleted one row in order to not break existing code. +}
\ No newline at end of file diff --git a/engine/lib/deprecated-1.8.php b/engine/lib/deprecated-1.8.php new file mode 100644 index 000000000..91068d047 --- /dev/null +++ b/engine/lib/deprecated-1.8.php @@ -0,0 +1,4820 @@ +<?php +/** + * *************************************************************************** + * NOTE: If this is ever removed from Elgg, sites lose the ability to upgrade + * from 1.7.x and earlier to the latest version of Elgg without upgrading to + * 1.8 first. + * *************************************************************************** + * + * Upgrade the database schema in an ordered sequence. + * + * Executes all upgrade files in elgg/engine/schema/upgrades/ in sequential order. + * Upgrade files must be in the standard Elgg release format of YYYYMMDDII.sql + * where II is an incrementor starting from 01. + * + * Files that are < $version will be ignored. + * + * @warning Plugin authors should not call this function directly. + * + * @param int    $version The version you are upgrading from in the format YYYYMMDDII. + * @param string $fromdir Optional directory to load upgrades from. default: engine/schema/upgrades/ + * @param bool   $quiet   If true, suppress all error messages. Only use for the upgrade from <=1.6. + * + * @return int The number of upgrades run. + * @see upgrade.php + * @see version.php + * @deprecated 1.8 Use PHP upgrades for sql changes. + */ +function db_upgrade($version, $fromdir = "", $quiet = FALSE) { +	global $CONFIG; + +	elgg_deprecated_notice('db_upgrade() is deprecated by using PHP upgrades.', 1.8); + +	$version = (int) $version; + +	if (!$fromdir) { +		$fromdir = $CONFIG->path . 'engine/schema/upgrades/'; +	} +	 +	$i = 0; + +	if ($handle = opendir($fromdir)) { +		$sqlupgrades = array(); + +		while ($sqlfile = readdir($handle)) { +			if (!is_dir($fromdir . $sqlfile)) { +				if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) { +					$sql_version = (int) $matches[1]; +					if ($sql_version > $version) { +						$sqlupgrades[] = $sqlfile; +					} +				} +			} +		} + +		asort($sqlupgrades); + +		if (sizeof($sqlupgrades) > 0) { +			foreach ($sqlupgrades as $sqlfile) { + +				// hide all errors. +				if ($quiet) { +					try { +						run_sql_script($fromdir . $sqlfile); +					} catch (DatabaseException $e) { +						error_log($e->getmessage()); +					} +				} else { +					run_sql_script($fromdir . $sqlfile); +				} +				$i++; +			} +		} +	} + +	return $i; +} + +/** + * Lists entities from an access collection + * + * @deprecated 1.8 Use elgg_list_entities_from_access_id() + * + * @return str + */ +function list_entities_from_access_id($access_id, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true) { + +	elgg_deprecated_notice("All list_entities* functions were deprecated in 1.8.  Use elgg_list_entities* instead.", 1.8); + +	echo elgg_list_entities_from_access_id(array('access_id' => $access_id, +		'type' => $entity_type, 'subtype' => $entity_subtype, 'owner_guids' => $owner_guid, +		'limit' => $limit, 'full_view' => $fullview, 'list_type_toggle' => $listtypetoggle, +		'pagination' => $pagination,)); +} + +/** + * Registers a particular action in memory + * + * @deprecated 1.8 Use {@link elgg_register_action()} instead + * + * @param string $action The name of the action (eg "register", "account/settings/save") + * @param boolean $public Can this action be accessed by people not logged into the system? + * @param string $filename Optionally, the filename where this action is located + * @param boolean $admin_only Whether this action is only available to admin users. + */ +function register_action($action, $public = false, $filename = "", $admin_only = false) { +	elgg_deprecated_notice("register_action() was deprecated by elgg_register_action()", 1.8); + +	if ($admin_only) { +		$access = 'admin'; +	} elseif ($public) { +		$access = 'public'; +	} else { +		$access = 'logged_in'; +	} + +	return elgg_register_action($action, $filename, $access); +} + +/** + * Register an admin page with the admin panel. + * This function extends the view "admin/main" with the provided view. + * This view should provide a description and either a control or a link to. + * + * @deprecated 1.8 Extend admin views manually + * + * Usage: + * 	- To add a control to the main admin panel then extend admin/main + *  - To add a control to a new page create a page which renders a view admin/subpage + *    (where subpage is your new page - + *    nb. some pages already exist that you can extend), extend the main view to point to it, + *    and add controls to your new view. + * + * At the moment this is essentially a wrapper around elgg_extend_view(). + * + * @param string $new_admin_view The view associated with the control you're adding + * @param string $view           The view to extend, by default this is 'admin/main'. + * @param int    $priority       Optional priority to govern the appearance in the list. + * + * @return void + */ +function extend_elgg_admin_page($new_admin_view, $view = 'admin/main', $priority = 500) { +	elgg_deprecated_notice('extend_elgg_admin_page() does nothing. Extend admin views manually.', 1.8); +} + +/** + * Get entities ordered by a mathematical calculation + * + * @deprecated 1.8 Use elgg_get_entities_from_annotation_calculation() + * + * @param string $sum            What sort of calculation to perform + * @param string $entity_type    Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name           Name of annotation + * @param string $mdname         Metadata name + * @param string $mdvalue        Metadata value + * @param int    $owner_guid     GUID of owner of annotation + * @param int    $limit          Limit of results + * @param int    $offset         Offset of results + * @param string $orderdir       Order of results + * @param bool   $count          Return count or entities + * + * @return mixed + */ +function get_entities_from_annotations_calculate_x($sum = "sum", $entity_type = "", $entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $owner_guid = 0, $limit = 10, $offset = 0, $orderdir = 'desc', $count = false) { + +	$msg = 'get_entities_from_annotations_calculate_x() is deprecated by elgg_get_entities_from_annotation_calculation().'; + +	elgg_deprecated_notice($msg, 1.8); + +	$options = array(); + +	$options['calculation'] = $sum; + +	if ($entity_type) { +		$options['types'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtypes'] = $entity_subtype; +	} + +	$options['annotation_names'] = $name; + +	if ($mdname) { +		$options['metadata_names'] = $mdname; +	} + +	if ($mdvalue) { +		$options['metadata_values'] = $mdvalue; +	} + +	// original function rewrote this to container guid. +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['container_guids'] = $owner_guid; +		} else { +			$options['container_guid'] = $owner_guid; +		} +	} + +	$options['limit'] = $limit; +	$options['offset'] = $offset; + +	$options['order_by'] = "annotation_calculation $orderdir"; + +	$options['count'] = $count; + +	return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Returns entities ordered by the sum of an annotation + * + * @warning This is function uses sum instead of count. THIS IS SLOW. See #3366. + *          This should be used when you have annotations with different values and you + *          want a list of entities ordered by the sum of all of those values. + *          If you want a list of entities ordered by the number of annotations on each entity, + *          use __get_entities_from_annotations_calculate_x() and pass 'count' as the first param. + * + * @deprecated 1.8 Use elgg_get_entities_from_annotation_calculation() + * + * @param string $entity_type    Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name           Name of annotation + * @param string $mdname         Metadata name + * @param string $mdvalue        Metadata value + * @param int    $owner_guid     GUID of owner of annotation + * @param int    $limit          Limit of results + * @param int    $offset         Offset of results + * @param string $orderdir       Order of results + * @param bool   $count          Return count or entities + * + * @return unknown + */ +function get_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $owner_guid = 0, $limit = 10, $offset = 0, $orderdir = 'desc', $count = false) { + +	$msg = 'get_entities_from_annotation_count() is deprecated by elgg_get_entities_from_annotation_calculation().'; + +	elgg_deprecated_notice($msg, 1.8); + +	$options = array(); + +	$options['calculation'] = 'sum'; + +	if ($entity_type) { +		$options['types'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtypes'] = $entity_subtype; +	} + +	$options['annotation_names'] = $name; + +	if ($mdname) { +		$options['metadata_names'] = $mdname; +	} + +	if ($mdvalue) { +		$options['metadata_values'] = $mdvalue; +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	$options['limit'] = $limit; +	$options['offset'] = $offset; + +	$options['order_by'] = "annotation_calculation $orderdir"; + +	$options['count'] = $count; + +	return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Lists entities by the totals of a particular kind of annotation + * + * @deprecated 1.8 Use elgg_list_entities_from_annotation_calculation() + * + * @param string  $entity_type    Type of entity. + * @param string  $entity_subtype Subtype of entity. + * @param string  $name           Name of annotation. + * @param int     $limit          Maximum number of results to return. + * @param int     $owner_guid     Owner. + * @param int     $group_guid     Group container. Currently only supported if entity_type is object + * @param boolean $asc            Whether to list in ascending or descending order (default: desc) + * @param boolean $fullview       Whether to display the entities in full + * @param boolean $listtypetoggle Can the 'gallery' view can be displayed (default: no) + * @param boolean $pagination     Add pagination + * @param string  $orderdir       Order desc or asc + * + * @return string Formatted entity list + */ +function list_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, $listtypetoggle = false, $pagination = true, $orderdir = 'desc') { + +	$msg = 'list_entities_from_annotation_count() is deprecated by elgg_list_entities_from_annotation_calculation().'; + +	elgg_deprecated_notice($msg, 1.8); + +	$options = array(); + +	$options['calculation'] = 'sum'; + +	if ($entity_type) { +		$options['types'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtypes'] = $entity_subtype; +	} + +	$options['annotation_names'] = $name; + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	$options['full_view'] = $fullview; + +	$options['list_type_toggle'] = $listtypetoggle; + +	$options['pagination'] = $pagination; + +	$options['limit'] = $limit; + +	$options['order_by'] = "annotation_calculation $orderdir"; + +	return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Adds an entry in $CONFIG[$register_name][$subregister_name]. + * + * @deprecated 1.8 Use the new menu system. + *  + * This is only used for the site-wide menu.  See {@link add_menu()}. + * + * @param string $register_name     The name of the top-level register + * @param string $subregister_name  The name of the subregister + * @param mixed  $subregister_value The value of the subregister + * @param array  $children_array    Optionally, an array of children + * + * @return true|false Depending on success + */ +function add_to_register($register_name, $subregister_name, $subregister_value, $children_array = array()) { +	elgg_deprecated_notice("add_to_register() has been deprecated", 1.8); +	global $CONFIG; + +	if (empty($register_name) || empty($subregister_name)) { +		return false; +	} + +	if (!isset($CONFIG->registers)) { +		$CONFIG->registers = array(); +	} + +	if (!isset($CONFIG->registers[$register_name])) { +		$CONFIG->registers[$register_name] = array(); +	} + +	$subregister = new stdClass; +	$subregister->name = $subregister_name; +	$subregister->value = $subregister_value; + +	if (is_array($children_array)) { +		$subregister->children = $children_array; +	} + +	$CONFIG->registers[$register_name][$subregister_name] = $subregister; +	return true; +} + +/** + * Removes a register entry from $CONFIG[register_name][subregister_name] + * + * @deprecated 1.8 Use the new menu system. + * + * This is used to by {@link remove_menu()} to remove site-wide menu items. + * + * @param string $register_name    The name of the top-level register + * @param string $subregister_name The name of the subregister + * + * @return true|false Depending on success + * @since 1.7.0 + */ +function remove_from_register($register_name, $subregister_name) { +	elgg_deprecated_notice("remove_from_register() has been deprecated", 1.8); +	global $CONFIG; + +	if (empty($register_name) || empty($subregister_name)) { +		return false; +	} + +	if (!isset($CONFIG->registers)) { +		return false; +	} + +	if (!isset($CONFIG->registers[$register_name])) { +		return false; +	} + +	if (isset($CONFIG->registers[$register_name][$subregister_name])) { +		unset($CONFIG->registers[$register_name][$subregister_name]); +		return true; +	} + +	return false; +} + +/** + * If it exists, returns a particular register as an array + * + * @deprecated 1.8 Use the new menu system + * + * @param string $register_name The name of the register + * + * @return array|false Depending on success + */ +function get_register($register_name) { +	elgg_deprecated_notice("get_register() has been deprecated", 1.8); +	global $CONFIG; + +	if ($register_name == 'menu') { +		// backward compatible code for site menu +		$menu = $CONFIG->menus['site']; +		$builder = new ElggMenuBuilder($menu); +		$menu_items = $builder->getMenu('text'); +		$menu_items = $menu_items['default']; + +		$menu = array(); +		foreach ($menu_items as $item) { +			$subregister = new stdClass; +			$subregister->name = $item->getText(); +			$subregister->value = $item->getHref(); +			$menu[$subregister->name] = $subregister; +		} +		return $menu; +	} + +	if (isset($CONFIG->registers[$register_name])) { +		return $CONFIG->registers[$register_name]; +	} + +	return false; +} + +/** + * Deprecated events core function. Code divided between elgg_register_event_handler() + * and trigger_elgg_event(). + * + * @deprecated 1.8 Use explicit register/trigger event functions + * + * @param string  $event       The type of event (eg 'init', 'update', 'delete') + * @param string  $object_type The type of object (eg 'system', 'blog', 'user') + * @param string  $function    The name of the function that will handle the event + * @param int     $priority    Priority to call handler. Lower numbers called first (default 500) + * @param boolean $call        Set to true to call the event rather than add to it (default false) + * @param mixed   $object      Optionally, the object the event is being performed on (eg a user) + * + * @return true|false Depending on success + */ +function events($event = "", $object_type = "", $function = "", $priority = 500, $call = false, $object = null) { + +	elgg_deprecated_notice('events() has been deprecated.', 1.8); + +	// leaving this here just in case someone was directly calling this internal function +	if (!$call) { +		return elgg_register_event_handler($event, $object_type, $function, $priority); +	} else { +		return trigger_elgg_event($event, $object_type, $object); +	} +} + +/** + * Alias function for events, that registers a function to a particular kind of event + * + * @deprecated 1.8 Use elgg_register_event_handler() instead + *  + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @return true|false Depending on success + */ +function register_elgg_event_handler($event, $object_type, $callback, $priority = 500) { +	elgg_deprecated_notice("register_elgg_event_handler() was deprecated by elgg_register_event_handler()", 1.8); +	return elgg_register_event_handler($event, $object_type, $callback, $priority); +} + +/** + * Unregisters a function to a particular kind of event + *  + * @deprecated 1.8 Use elgg_unregister_event_handler instead + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @since 1.7.0 + */ +function unregister_elgg_event_handler($event, $object_type, $callback) { +	elgg_deprecated_notice('unregister_elgg_event_handler => elgg_unregister_event_handler', 1.8); +	elgg_unregister_event_handler($event, $object_type, $callback); +} + +/** + * Alias function for events, that triggers a particular kind of event + * + * @deprecated 1.8 Use elgg_trigger_event() instead + *  + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @return true|false Depending on success + */ +function trigger_elgg_event($event, $object_type, $object = null) { +	elgg_deprecated_notice('trigger_elgg_event() was deprecated by elgg_trigger_event()', 1.8); +	return elgg_trigger_event($event, $object_type, $object); +} + +/** + * Register a function to a plugin hook for a particular entity type, with a given priority. + * + * @deprecated 1.8 Use elgg_register_plugin_hook_handler() instead + *  + * eg if you want the function "export_user" to be called when the hook "export" for "user" entities + * is run, use: + * + * 		register_plugin_hook("export", "user", "export_user"); + * + * "all" is a valid value for both $hook and $entity_type. "none" is a valid value for $entity_type. + * + * The export_user function would then be defined as: + * + * 		function export_user($hook, $entity_type, $returnvalue, $params); + * + * Where $returnvalue is the return value returned by the last function returned by the hook, and + * $params is an array containing a set of parameters (or nothing). + * + * @param string $hook The name of the hook + * @param string $entity_type The name of the type of entity (eg "user", "object" etc) + * @param string $function The name of a valid function to be run + * @param string $priority The priority - 0 is first, 1000 last, default is 500 + * @return true|false Depending on success + */ +function register_plugin_hook($hook, $type, $callback, $priority = 500) { +	elgg_deprecated_notice("register_plugin_hook() was deprecated by elgg_register_plugin_hook_handler()", 1.8); +	return elgg_register_plugin_hook_handler($hook, $type, $callback, $priority); +} + +/** + * Unregister a function to a plugin hook for a particular entity type + * + * @deprecated 1.8 Use elgg_unregister_plugin_hook_handler() instead + *  + * @param string $hook The name of the hook + * @param string $entity_type The name of the type of entity (eg "user", "object" etc) + * @param string $function The name of a valid function to be run + * @since 1.7.0 + */ +function unregister_plugin_hook($hook, $entity_type, $callback) { +	elgg_deprecated_notice("unregister_plugin_hook() was deprecated by elgg_unregister_plugin_hook_handler()", 1.8); +	elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback); +} + +/** + * Triggers a plugin hook, with various parameters as an array. For example, to provide + * a 'foo' hook that concerns an entity of type 'bar', with a parameter called 'param1' + * with value 'value1', that by default returns true, you'd call: + * + * @deprecated 1.8 Use elgg_trigger_plugin_hook() instead + * + * trigger_plugin_hook('foo', 'bar', array('param1' => 'value1'), true); + * + * @see register_plugin_hook + * @param string $hook The name of the hook to trigger + * @param string $entity_type The name of the entity type to trigger it for (or "all", or "none") + * @param array $params Any parameters. It's good practice to name the keys, i.e. by using array('name' => 'value', 'name2' => 'value2') + * @param mixed $returnvalue An initial return value + * @return mixed|null The cumulative return value for the plugin hook functions + */ +function trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) { +	elgg_deprecated_notice("trigger_plugin_hook() was deprecated by elgg_trigger_plugin_hook()", 1.8); +	return elgg_trigger_plugin_hook($hook, $type, $params, $returnvalue); +} + +/** + * Checks if code is being called from a certain function. + * + * To use, call this function with the function name (and optional + * file location) that it has to be called from, it will either + * return true or false. + * + * e.g. + * + * function my_secure_function() + * { + * 		if (!call_gatekeeper("my_call_function")) + * 			return false; + * + * 		... do secure stuff ... + * } + * + * function my_call_function() + * { + * 		// will work + * 		my_secure_function(); + * } + * + * function bad_function() + * { + * 		// Will not work + * 		my_secure_function(); + * } + * + * @param mixed  $function The function that this function must have in its call stack, + * 		                   to test against a method pass an array containing a class and + *                         method name. + * @param string $file     Optional file that the function must reside in. + * + * @return bool + * + * @deprecated 1.8 A neat but pointless function + */ +function call_gatekeeper($function, $file = "") { +	elgg_deprecated_notice("call_gatekeeper() is neat but pointless", 1.8); +	// Sanity check +	if (!$function) { +		return false; +	} + +	// Check against call stack to see if this is being called from the correct location +	$callstack = debug_backtrace(); +	$stack_element = false; + +	foreach ($callstack as $call) { +		if (is_array($function)) { +			if ((strcmp($call['class'], $function[0]) == 0) && (strcmp($call['function'], $function[1]) == 0)) { +				$stack_element = $call; +			} +		} else { +			if (strcmp($call['function'], $function) == 0) { +				$stack_element = $call; +			} +		} +	} + +	if (!$stack_element) { +		return false; +	} + +	// If file then check that this it is being called from this function +	if ($file) { +		$mirror = null; + +		if (is_array($function)) { +			$mirror = new ReflectionMethod($function[0], $function[1]); +		} else { +			$mirror = new ReflectionFunction($function); +		} + +		if ((!$mirror) || (strcmp($file, $mirror->getFileName()) != 0)) { +			return false; +		} +	} + +	return true; +} + +/** + * This function checks to see if it is being called at somepoint by a function defined somewhere + * on a given path (optionally including subdirectories). + * + * This function is similar to call_gatekeeper() but returns true if it is being called + * by a method or function which has been defined on a given path or by a specified file. + * + * @param string $path            The full path and filename that this function must have + *                                in its call stack If a partial path is given and + *                                $include_subdirs is true, then the function will return + *                                true if called by any function in or below the specified path. + * @param bool   $include_subdirs Are subdirectories of the path ok, or must you specify an + *                                absolute path and filename. + * @param bool   $strict_mode     If true then the calling method or function must be directly + *                                called by something on $path, if false the whole call stack is + *                                searched. + * + * @return void + * + * @deprecated 1.8 A neat but pointless function + */ +function callpath_gatekeeper($path, $include_subdirs = true, $strict_mode = false) { +	elgg_deprecated_notice("callpath_gatekeeper() is neat but pointless", 1.8); + +	global $CONFIG; + +	$path = sanitise_string($path); + +	if ($path) { +		$callstack = debug_backtrace(); + +		foreach ($callstack as $call) { +			$call['file'] = str_replace("\\", "/", $call['file']); + +			if ($include_subdirs) { +				if (strpos($call['file'], $path) === 0) { + +					if ($strict_mode) { +						$callstack[1]['file'] = str_replace("\\", "/", $callstack[1]['file']); +						if ($callstack[1] === $call) { +							return true; +						} +					} else { +						return true; +					} +				} +			} else { +				if (strcmp($path, $call['file']) == 0) { +					if ($strict_mode) { +						if ($callstack[1] === $call) { +							return true; +						} +					} else { +						return true; +					} +				} +			} + +		} +		return false; +	} + +	if (isset($CONFIG->debug)) { +		system_message("Gatekeeper'd function called from {$callstack[1]['file']}:" . "{$callstack[1]['line']}\n\nStack trace:\n\n" . print_r($callstack, true)); +	} + +	return false; +} + +/** + * Returns SQL where clause for owner and containers. + * + * @deprecated 1.8 Use elgg_get_guid_based_where_sql(); + * + * @param string     $table       Entity table prefix as defined in SELECT...FROM entities $table + * @param NULL|array $owner_guids Owner GUIDs + * + * @return FALSE|str + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_owner_where_sql($table, $owner_guids) { +	elgg_deprecated_notice('elgg_get_entity_owner_where_sql() is deprecated by elgg_get_guid_based_where_sql().', 1.8); + +	return elgg_get_guid_based_where_sql("{$table}.owner_guid", $owner_guids); +} + +/** + * Returns SQL where clause for containers. + * + * @deprecated 1.8 Use elgg_get_guid_based_where_sql(); + * + * @param string     $table           Entity table prefix as defined in + *                                    SELECT...FROM entities $table + * @param NULL|array $container_guids Array of container guids + * + * @return FALSE|string + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_container_where_sql($table, $container_guids) { +	elgg_deprecated_notice('elgg_get_entity_container_where_sql() is deprecated by elgg_get_guid_based_where_sql().', 1.8); + +	return elgg_get_guid_based_where_sql("{$table}.container_guid", $container_guids); +} + +/** + * Returns SQL where clause for site entities + * + * @deprecated 1.8 Use elgg_get_guid_based_where_sql() + * + * @param string     $table      Entity table prefix as defined in SELECT...FROM entities $table + * @param NULL|array $site_guids Array of site guids + * + * @return FALSE|string + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_site_where_sql($table, $site_guids) { +	elgg_deprecated_notice('elgg_get_entity_site_where_sql() is deprecated by elgg_get_guid_based_where_sql().', 1.8); + +	return elgg_get_guid_based_where_sql("{$table}.site_guid", $site_guids); +} + +/** + * Return an array of objects in a given container. + * + * @see get_entities() + * + * @param int    $group_guid The container (defaults to current page owner) + * @param string $subtype    The subtype + * @param int    $owner_guid Owner + * @param int    $site_guid  The site + * @param string $order_by   Order + * @param int    $limit      Limit on number of elements to return, by default 10. + * @param int    $offset     Where to start, by default 0. + * @param bool   $count      Whether to return the entities or a count of them. + * + * @return array|false + * @deprecated 1.8 Use elgg_get_entities() instead + */ +function get_objects_in_group($group_guid, $subtype = "", $owner_guid = 0, $site_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = FALSE) { +	elgg_deprecated_notice("get_objects_in_group was deprected in 1.8.  Use elgg_get_entities() instead", 1.8); + +	global $CONFIG; + +	if ($subtype === FALSE || $subtype === null || $subtype === 0) { +		return FALSE; +	} + +	if ($order_by == "") { +		$order_by = "e.time_created desc"; +	} +	$order_by = sanitise_string($order_by); +	$limit = (int)$limit; +	$offset = (int)$offset; +	$site_guid = (int)$site_guid; +	if ($site_guid == 0) { +		$site_guid = $CONFIG->site_guid; +	} + +	$container_guid = (int)$group_guid; +	if ($container_guid == 0) { +		$container_guid = elgg_get_page_owner_guid(); +	} + +	$where = array(); + +	$where[] = "e.type='object'"; + +	if (!empty($subtype)) { +		if (!$subtype = get_subtype_id('object', $subtype)) { +			return FALSE; +		} +		$where[] = "e.subtype=$subtype"; +	} +	if ($owner_guid != "") { +		if (!is_array($owner_guid)) { +			$owner_guid = (int)$owner_guid; +			$where[] = "e.container_guid = '$owner_guid'"; +		} else if (sizeof($owner_guid) > 0) { +			// Cast every element to the owner_guid array to int +			$owner_guid = array_map("sanitise_int", $owner_guid); +			$owner_guid = implode(",", $owner_guid); +			$where[] = "e.container_guid in ({$owner_guid})"; +		} +	} +	if ($site_guid > 0) { +		$where[] = "e.site_guid = {$site_guid}"; +	} + +	if ($container_guid > 0) { +		$where[] = "e.container_guid = {$container_guid}"; +	} + +	if (!$count) { +		$query = "SELECT * from {$CONFIG->dbprefix}entities e" . " join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid where "; +	} else { +		$query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e" . " join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid where "; +	} +	foreach ($where as $w) { +		$query .= " $w and "; +	} + +	// Add access controls +	$query .= get_access_sql_suffix('e'); +	if (!$count) { +		$query .= " order by $order_by"; + +		// Add order and limit +		if ($limit) { +			$query .= " limit $offset, $limit"; +		} + +		$dt = get_data($query, "entity_row_to_elggstar"); +		return $dt; +	} else { +		$total = get_data_row($query); +		return $total->total; +	} +} + +/** + * Lists entities that belong to a group. + * + * @param string $subtype        The arbitrary subtype of the entity + * @param int    $owner_guid     The GUID of the owning user + * @param int    $container_guid The GUID of the containing group + * @param int    $limit          The number of entities to display per page (default: 10) + * @param bool   $fullview       Whether or not to display the full view (default: true) + * @param bool   $listtypetoggle Whether or not to allow gallery view (default: true) + * @param bool   $pagination     Whether to display pagination (default: true) + * + * @return string List of parsed entities + * + * @see elgg_list_entities() + * @deprecated 1.8 Use elgg_list_entities() instead + */ +function list_entities_groups($subtype = "", $owner_guid = 0, $container_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true) { +	elgg_deprecated_notice("list_entities_groups was deprecated in 1.8.  Use elgg_list_entities() instead.", 1.8); +	$offset = (int)get_input('offset'); +	$count = get_objects_in_group($container_guid, $subtype, $owner_guid, 0, "", $limit, $offset, true); +	$entities = get_objects_in_group($container_guid, $subtype, $owner_guid, 0, "", $limit, $offset); + +	return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, $pagination); +} + +/** + * Get all the entities from metadata from a group. + * + * @param int    $group_guid     The ID of the group. + * @param mixed  $meta_name      Metadata name + * @param mixed  $meta_value     Metadata value + * @param string $entity_type    The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int    $owner_guid     Owner guid + * @param int    $limit          Limit + * @param int    $offset         Offset + * @param string $order_by       Optional ordering. + * @param int    $site_guid      Site GUID. 0 for current, -1 for any + * @param bool   $count          Return count instead of entities + * + * @return array|false + * @deprecated 1.8 Use elgg_get_entities_from_metadata() + */ +function get_entities_from_metadata_groups($group_guid, $meta_name, $meta_value = "", $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { +	elgg_deprecated_notice("get_entities_from_metadata_groups was deprecated in 1.8.", 1.8); +	global $CONFIG; + +	$meta_n = get_metastring_id($meta_name); +	$meta_v = get_metastring_id($meta_value); + +	$entity_type = sanitise_string($entity_type); +	$entity_subtype = get_subtype_id($entity_type, $entity_subtype); +	$limit = (int)$limit; +	$offset = (int)$offset; +	if ($order_by == "") { +		$order_by = "e.time_created desc"; +	} +	$order_by = sanitise_string($order_by); +	$site_guid = (int)$site_guid; +	if (is_array($owner_guid)) { +		foreach ($owner_guid as $key => $guid) { +			$owner_guid[$key] = (int)$guid; +		} +	} else { +		$owner_guid = (int)$owner_guid; +	} +	if ($site_guid == 0) { +		$site_guid = $CONFIG->site_guid; +	} + +	$container_guid = (int)$group_guid; +	if ($container_guid == 0) { +		$container_guid = elgg_get_page_owner_guid(); +	} + +	$where = array(); + +	if ($entity_type != "") { +		$where[] = "e.type='$entity_type'"; +	} +	if ($entity_subtype) { +		$where[] = "e.subtype=$entity_subtype"; +	} +	if ($meta_name != "") { +		$where[] = "m.name_id='$meta_n'"; +	} +	if ($meta_value != "") { +		$where[] = "m.value_id='$meta_v'"; +	} +	if ($site_guid > 0) { +		$where[] = "e.site_guid = {$site_guid}"; +	} +	if ($container_guid > 0) { +		$where[] = "e.container_guid = {$container_guid}"; +	} + +	if (is_array($owner_guid)) { +		$where[] = "e.container_guid in (" . implode(",", $owner_guid) . ")"; +	} else if ($owner_guid > 0) { +		$where[] = "e.container_guid = {$owner_guid}"; +	} + +	if (!$count) { +		$query = "SELECT distinct e.* "; +	} else { +		$query = "SELECT count(e.guid) as total "; +	} + +	$query .= "from {$CONFIG->dbprefix}entities e" . " JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid " . " JOIN {$CONFIG->dbprefix}objects_entity o on e.guid = o.guid where"; + +	foreach ($where as $w) { +		$query .= " $w and "; +	} + +	// Add access controls +	$query .= get_access_sql_suffix("e"); + +	if (!$count) { +		$query .= " order by $order_by limit $offset, $limit"; // Add order and limit +		return get_data($query, "entity_row_to_elggstar"); +	} else { +		if ($row = get_data_row($query)) { +			return $row->total; +		} +	} +	return false; +} + +/** + * As get_entities_from_metadata_groups() but with multiple entities. + * + * @param int    $group_guid     The ID of the group. + * @param array  $meta_array     Array of 'name' => 'value' pairs + * @param string $entity_type    The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int    $owner_guid     Owner GUID + * @param int    $limit          Limit + * @param int    $offset         Offset + * @param string $order_by       Optional ordering. + * @param int    $site_guid      Site GUID. 0 for current, -1 for any + * @param bool   $count          Return count of entities instead of entities + * + * @return int|array List of ElggEntities, or the total number if count is set to false + * @deprecated 1.8 Use elgg_get_entities_from_metadata() + */ +function get_entities_from_metadata_groups_multi($group_guid, $meta_array, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { +	elgg_deprecated_notice("get_entities_from_metadata_groups_multi was deprecated in 1.8.", 1.8); + +	global $CONFIG; + +	if (!is_array($meta_array) || sizeof($meta_array) == 0) { +		return false; +	} + +	$where = array(); + +	$mindex = 1; +	$join = ""; +	foreach ($meta_array as $meta_name => $meta_value) { +		$meta_n = get_metastring_id($meta_name); +		$meta_v = get_metastring_id($meta_value); +		$join .= " JOIN {$CONFIG->dbprefix}metadata m{$mindex} on e.guid = m{$mindex}.entity_guid" . " JOIN {$CONFIG->dbprefix}objects_entity o on e.guid = o.guid "; + +		if ($meta_name != "") { +			$where[] = "m{$mindex}.name_id='$meta_n'"; +		} + +		if ($meta_value != "") { +			$where[] = "m{$mindex}.value_id='$meta_v'"; +		} + +		$mindex++; +	} + +	$entity_type = sanitise_string($entity_type); +	$entity_subtype = get_subtype_id($entity_type, $entity_subtype); +	$limit = (int)$limit; +	$offset = (int)$offset; +	if ($order_by == "") { +		$order_by = "e.time_created desc"; +	} +	$order_by = sanitise_string($order_by); +	$owner_guid = (int)$owner_guid; + +	$site_guid = (int)$site_guid; +	if ($site_guid == 0) { +		$site_guid = $CONFIG->site_guid; +	} + +	//$access = get_access_list(); + +	if ($entity_type != "") { +		$where[] = "e.type = '{$entity_type}'"; +	} + +	if ($entity_subtype) { +		$where[] = "e.subtype = {$entity_subtype}"; +	} + +	if ($site_guid > 0) { +		$where[] = "e.site_guid = {$site_guid}"; +	} + +	if ($owner_guid > 0) { +		$where[] = "e.owner_guid = {$owner_guid}"; +	} + +	if ($container_guid > 0) { +		$where[] = "e.container_guid = {$container_guid}"; +	} + +	if ($count) { +		$query = "SELECT count(e.guid) as total "; +	} else { +		$query = "SELECT distinct e.* "; +	} + +	$query .= " from {$CONFIG->dbprefix}entities e {$join} where"; +	foreach ($where as $w) { +		$query .= " $w and "; +	} +	$query .= get_access_sql_suffix("e"); // Add access controls + +	if (!$count) { +		$query .= " order by $order_by limit $offset, $limit"; // Add order and limit +		return get_data($query, "entity_row_to_elggstar"); +	} else { +		if ($count = get_data_row($query)) { +			return $count->total; +		} +	} +	return false; +} + +/** + * List items within a given geographic area. + * + * @param real   $lat            Latitude + * @param real   $long           Longitude + * @param real   $radius         The radius + * @param string $type           The type of entity (eg "user", "object" etc) + * @param string $subtype        The arbitrary subtype of the entity + * @param int    $owner_guid     The GUID of the owning user + * @param int    $limit          The number of entities to display per page (default: 10) + * @param bool   $fullview       Whether or not to display the full view (default: true) + * @param bool   $listtypetoggle Whether or not to allow gallery view + * @param bool   $navigation     Display pagination? Default: true + * + * @return string A viewable list of entities + * @deprecated 1.8 Use elgg_get_entities_from_location() + */ +function list_entities_in_area($lat, $long, $radius, $type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { +	elgg_deprecated_notice('list_entities_in_area() was deprecated. Use elgg_list_entities_from_location()', 1.8); + +	$options = array(); + +	$options['latitude'] = $lat; +	$options['longitude'] = $long; +	$options['distance'] = $radius; + +	if ($type) { +		$options['types'] = $type; +	} + +	if ($subtype) { +		$options['subtypes'] = $subtype; +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	$options['limit'] = $limit; + +	$options['full_view'] = $fullview; +	$options['list_type_toggle'] = $listtypetoggle; +	$options['pagination'] = $pagination; + +	return elgg_list_entities_from_location($options); +} + +/** + * List entities in a given location + * + * @param string $location       Location + * @param string $type           The type of entity (eg "user", "object" etc) + * @param string $subtype        The arbitrary subtype of the entity + * @param int    $owner_guid     The GUID of the owning user + * @param int    $limit          The number of entities to display per page (default: 10) + * @param bool   $fullview       Whether or not to display the full view (default: true) + * @param bool   $listtypetoggle Whether or not to allow gallery view + * @param bool   $navigation     Display pagination? Default: true + * + * @return string A viewable list of entities + * @deprecated 1.8 Use elgg_list_entities_from_location() + */ +function list_entities_location($location, $type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { +	elgg_deprecated_notice('list_entities_location() was deprecated. Use elgg_list_entities_from_metadata()', 1.8); + +	return list_entities_from_metadata('location', $location, $type, $subtype, $owner_guid, $limit, $fullview, $listtypetoggle, $navigation); +} + +/** + * Return entities within a given geographic area. + * + * @param float     $lat            Latitude + * @param float     $long           Longitude + * @param float     $radius         The radius + * @param string    $type           The type of entity (eg "user", "object" etc) + * @param string    $subtype        The arbitrary subtype of the entity + * @param int       $owner_guid     The GUID of the owning user + * @param string    $order_by       The field to order by; by default, time_created desc + * @param int       $limit          The number of entities to return; 10 by default + * @param int       $offset         The indexing offset, 0 by default + * @param boolean   $count          Count entities + * @param int       $site_guid      Site GUID. 0 for current, -1 for any + * @param int|array $container_guid Container GUID + * + * @return array A list of entities. + * @deprecated 1.8 Use elgg_get_entities_from_location() + */ +function get_entities_in_area($lat, $long, $radius, $type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = NULL) { +	elgg_deprecated_notice('get_entities_in_area() was deprecated by elgg_get_entities_from_location()!', 1.8); + +	$options = array(); + +	$options['latitude'] = $lat; +	$options['longitude'] = $long; +	$options['distance'] = $radius; + +	// set container_guid to owner_guid to emulate old functionality +	if ($owner_guid != "") { +		if (is_null($container_guid)) { +			$container_guid = $owner_guid; +		} +	} + +	if ($type) { +		$options['types'] = $type; +	} + +	if ($subtype) { +		$options['subtypes'] = $subtype; +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	if ($container_guid) { +		if (is_array($container_guid)) { +			$options['container_guids'] = $container_guid; +		} else { +			$options['container_guid'] = $container_guid; +		} +	} + +	$options['limit'] = $limit; + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($order_by) { +		$options['order_by']; +	} + +	if ($site_guid) { +		$options['site_guid']; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	return elgg_get_entities_from_location($options); +} + +/** + * Return a list of entities suitable for display based on the given search criteria. + * + * @see elgg_view_entity_list + * + * @deprecated 1.8 Use elgg_list_entities_from_metadata + * + * @param mixed  $meta_name      Metadata name to search on + * @param mixed  $meta_value     The value to match, optionally + * @param string $entity_type    The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity + * @param int    $owner_guid     Owner GUID + * @param int    $limit          Number of entities to display per page + * @param bool   $fullview       WDisplay the full view (default: true) + * @param bool   $listtypetoggle Allow users to toggle to the gallery view. Default: true + * @param bool   $pagination     Display pagination? Default: true + * @param bool   $case_sensitive Case sensitive metadata names? + * + * @return string + * + * @return string A list of entities suitable for display + */ +function list_entities_from_metadata($meta_name, $meta_value = "", $entity_type = ELGG_ENTITIES_ANY_VALUE, $entity_subtype = ELGG_ENTITIES_ANY_VALUE, $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true, $case_sensitive = true) { + +	elgg_deprecated_notice('list_entities_from_metadata() was deprecated by elgg_list_entities_from_metadata()!', 1.8); + +	$offset = (int)get_input('offset'); +	$limit = (int)$limit; +	$options = array( +		'metadata_name' => $meta_name, +		'metadata_value' => $meta_value, +		'type' => $entity_type, +		'subtype' => $entity_subtype, +		'limit' => $limit, +		'offset' => $offset, +		'count' => TRUE, +		'metadata_case_sensitive' => $case_sensitive +	); + +	// previous function allowed falsy $owner_guid for anything +	if ($owner_guid) { +		$options['owner_guid'] = $owner_guid; +	} + +	$count = elgg_get_entities_from_metadata($options); + +	$options['count'] = FALSE; +	$entities = elgg_get_entities_from_metadata($options); + +	return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, $pagination); +} + +/** + * Returns a viewable list of entities based on the given search criteria. + * + * @see elgg_view_entity_list + * + * @param array  $meta_array     Array of 'name' => 'value' pairs + * @param string $entity_type    The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int    $owner_guid     Owner GUID + * @param int    $limit          Limit + * @param bool   $fullview       WDisplay the full view (default: true) + * @param bool   $listtypetoggle Allow users to toggle to the gallery view. Default: true + * @param bool   $pagination     Display pagination? Default: true + * + * @return string List of ElggEntities suitable for display + * + * @deprecated 1.8 Use elgg_list_entities_from_metadata() instead + */ +function list_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true) { +	elgg_deprecated_notice(elgg_echo('deprecated:function', array( +		'list_entities_from_metadata_multi', 'elgg_get_entities_from_metadata')), 1.8); + +	$offset = (int)get_input('offset'); +	$limit = (int)$limit; +	$count = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", $site_guid, true); +	$entities = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", $site_guid, false); + +	return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, $pagination); +} + +/** + * Deprecated by elgg_register_menu_item(). Set $menu_name to 'page'. + * + * @see elgg_register_menu_item() + * @deprecated 1.8 Use the new menu system + * + * @param string  $label    The label + * @param string  $link     The link + * @param string  $group    The group to store item in + * @param boolean $onclick  Add a confirmation when clicked? + * @param boolean $selected Is menu item selected + * + * @return bool + */ +function add_submenu_item($label, $link, $group = 'default', $onclick = false, $selected = NULL) { +	elgg_deprecated_notice('add_submenu_item was deprecated by elgg_register_menu_item', 1.8); + +	// submenu items were added in the page setup hook usually by checking +	// the context.  We'll pass in the current context here, which will +	// emulate that effect. +	// if context == 'main' (default) it probably means they always wanted +	// the menu item to show up everywhere. +	$context = elgg_get_context(); + +	if ($context == 'main') { +		$context = 'all'; +	} + +	$item = array('name' => $label, 'text' => $label, 'href' => $link, 'context' => $context, +		'section' => $group,); + +	if ($selected) { +		$item['selected'] = true; +	} + +	if ($onclick) { +		$js = "onclick=\"javascript:return confirm('" . elgg_echo('deleteconfirm') . "')\""; +		$item['vars'] = array('js' => $js); +	} + +	return elgg_register_menu_item('page', $item); +} + +/** + * Remove an item from submenu by label + * + * @deprecated 1.8 Use the new menu system + * @see elgg_unregister_menu_item() + * + * @param string $label The item label + * @param string $group The submenu group (default "a") + * @return bool whether the item was removed or not + * @since 1.7.8 + */ +function remove_submenu_item($label, $group = 'a') { +	elgg_deprecated_notice('remove_submenu_item was deprecated by elgg_unregister_menu_item', 1.8); + +	return elgg_unregister_menu_item('page', $label); +} + +/** + * Use elgg_view_menu(). Set $menu_name to 'owner_block'. + * + * @see elgg_view_menu() + * @deprecated 1.8 Use the new menu system. elgg_view_menu() + * + * @return string + */ +function get_submenu() { +	elgg_deprecated_notice("get_submenu() has been deprecated by elgg_view_menu()", 1.8); +	return elgg_view_menu('owner_block', array('entity' => $owner, +		'class' => 'elgg-menu-owner-block',)); +} + +/** + * Adds an item to the site-wide menu. + * + * You can obtain the menu array by calling {@link get_register('menu')} + * + * @param string $menu_name     The name of the menu item + * @param string $menu_url      The URL of the page + * @param array  $menu_children Optionally, an array of submenu items (not used) + * @param string $context       (not used) + * + * @return true|false Depending on success + * @deprecated 1.8 use elgg_register_menu_item() for the menu 'site' + */ +function add_menu($menu_name, $menu_url, $menu_children = array(), $context = "") { +	elgg_deprecated_notice('add_menu() deprecated by elgg_register_menu_item()', 1.8); + +	return elgg_register_menu_item('site', array('name' => $menu_name, 'text' => $menu_name, +		'href' => $menu_url,)); +} + +/** + * Removes an item from the menu register + * + * @param string $menu_name The name of the menu item + * + * @return true|false Depending on success + * @deprecated 1.8 Use the new menu system + */ +function remove_menu($menu_name) { +	elgg_deprecated_notice("remove_menu() deprecated by elgg_unregister_menu_item()", 1.8); +	return elgg_unregister_menu_item('site', $menu_name); +} + +/** + * When given a title, returns a version suitable for inclusion in a URL + * + * @param string $title The title + * + * @return string The optimised title + * @deprecated 1.8 Use elgg_get_friendly_title() + */ +function friendly_title($title) { +	elgg_deprecated_notice('friendly_title was deprecated by elgg_get_friendly_title', 1.8); +	return elgg_get_friendly_title($title); +} + +/** + * Displays a UNIX timestamp in a friendly way (eg "less than a minute ago") + * + * @param int $time A UNIX epoch timestamp + * + * @return string The friendly time + * @deprecated 1.8 Use elgg_view_friendly_time() + */ +function friendly_time($time) { +	elgg_deprecated_notice('friendly_time was deprecated by elgg_view_friendly_time', 1.8); +	return elgg_view_friendly_time($time); +} + +/** + * Filters a string into an array of significant words + * + * @deprecated 1.8 Don't use this. + * + * @param string $string A string + * + * @return array + */ +function filter_string($string) { +	elgg_deprecated_notice('filter_string() was deprecated!', 1.8); + +	// Convert it to lower and trim +	$string = strtolower($string); +	$string = trim($string); + +	// Remove links and email addresses +	// match protocol://address/path/file.extension?some=variable&another=asf% +	$string = preg_replace("/\s([a-zA-Z]+:\/\/[a-z][a-z0-9\_\.\-]*[a-z]{2,6}" . "[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu", " ", $string); + +	// match www.something.domain/path/file.extension?some=variable&another=asf% +	$string = preg_replace("/\s(www\.[a-z][a-z0-9\_\.\-]*[a-z]{2,6}" . "[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu", " ", $string); + +	// match name@address +	$string = preg_replace("/\s([a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]" . "*\@[a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]{2,6})([\s|\.|\,])/iu", " ", $string); + +	// Sanitise the string; remove unwanted characters +	$string = preg_replace('/\W/ui', ' ', $string); + +	// Explode it into an array +	$terms = explode(' ', $string); + +	// Remove any blacklist terms +	//$terms = array_filter($terms, 'remove_blacklist'); + +	return $terms; +} + +/** + * Returns true if the word in $input is considered significant + * + * @deprecated 1.8 Don't use this. + * + * @param string $input A word + * + * @return true|false + */ +function remove_blacklist($input) { +	elgg_deprecated_notice('remove_blacklist() was deprecated!', 1.8); + +	global $CONFIG; + +	if (!is_array($CONFIG->wordblacklist)) { +		return $input; +	} + +	if (strlen($input) < 3 || in_array($input, $CONFIG->wordblacklist)) { +		return false; +	} + +	return true; +} + +/** + * Gets the guid of the entity that owns the current page. + * + * @deprecated 1.8  Use elgg_get_page_owner_guid() + * + * @return int The current page owner guid (0 if none). + */ +function page_owner() { +	elgg_deprecated_notice('page_owner() was deprecated by elgg_get_page_owner_guid().', 1.8); +	return elgg_get_page_owner_guid(); +} + +/** + * Gets the owner entity for the current page. + * + * @deprecated 1.8  Use elgg_get_page_owner_entity() + * @return ElggEntity|false The current page owner or false if none. + */ +function page_owner_entity() { +	elgg_deprecated_notice('page_owner_entity() was deprecated by elgg_get_page_owner_entity().', 1.8); +	return elgg_get_page_owner_entity(); +} + +/** + * Registers a page owner handler function + * + * @param string $functionname The callback function + * + * @deprecated 1.8  Use the 'page_owner', 'system' plugin hook + * @return void + */ +function add_page_owner_handler($functionname) { +	elgg_deprecated_notice("add_page_owner_handler() was deprecated by the plugin hook 'page_owner', 'system'.", 1.8); +} + +/** + * Set a page owner entity + * + * @param int $entitytoset The GUID of the entity + * + * @deprecated 1.8  Use elgg_set_page_owner_guid() + * @return void + */ +function set_page_owner($entitytoset = -1) { +	elgg_deprecated_notice('set_page_owner() was deprecated by elgg_set_page_owner_guid().', 1.8); +	elgg_set_page_owner_guid($entitytoset); +} + +/** + * Sets the functional context of a page + * + * @deprecated 1.8  Use elgg_set_context() + * + * @param string $context The context of the page + * + * @return mixed Either the context string, or false on failure + */ +function set_context($context) { +	elgg_deprecated_notice('set_context() was deprecated by elgg_set_context().', 1.8); +	elgg_set_context($context); +	if (empty($context)) { +		return false; +	} +	return $context; +} + +/** + * Returns the functional context of a page + * + * @deprecated 1.8  Use elgg_get_context() + * + * @return string The context, or 'main' if no context has been provided + */ +function get_context() { +	elgg_deprecated_notice('get_context() was deprecated by elgg_get_context().', 1.8); +	return elgg_get_context(); + +	// @todo - used to set context based on calling script +	// $context = get_plugin_name(true) +} + +/** + * Returns a list of plugins to load, in the order that they should be loaded. + * + * @deprecated 1.8 Use elgg_get_plugin_ids_in_dir() or elgg_get_plugins() + * + * @return array List of plugins + */ +function get_plugin_list() { +	elgg_deprecated_notice('get_plugin_list() is deprecated by elgg_get_plugin_ids_in_dir() or elgg_get_plugins()', 1.8); + +	$plugins = elgg_get_plugins('any'); + +	$list = array(); +	if ($plugins) { +		foreach ($plugins as $i => $plugin) { +			// in <=1.7 this returned indexed by multiples of 10. +			// uh...sure...why not. +			$index = ($i + 1) * 10; +			$list[$index] = $plugin->getID(); +		} +	} + +	return $list; +} + +/** + * Regenerates the list of known plugins and saves it to the current site + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * 		elgg_regenerate_simplecache(); + *		elgg_reset_system_cache(); + * + * @deprecated 1.8 Use elgg_generate_plugin_entities() and elgg_set_plugin_priorities() + * + * @param array $pluginorder Optionally, a list of existing plugins and their orders + * + * @return array The new list of plugins and their orders + */ +function regenerate_plugin_list($pluginorder = FALSE) { +	$msg = 'regenerate_plugin_list() is (sorta) deprecated by elgg_generate_plugin_entities() and' +			. ' elgg_set_plugin_priorities().'; +	elgg_deprecated_notice($msg, 1.8); + +	// they're probably trying to set it? +	if ($pluginorder) { +		if (elgg_generate_plugin_entities()) { +			// sort the plugins by the index numerically since we used +			// weird indexes in the old system. +			ksort($pluginorder, SORT_NUMERIC); +			return elgg_set_plugin_priorities($pluginorder); +		} +		return false; +	} else { +		// they're probably trying to regenerate from disk? +		return elgg_generate_plugin_entities(); +	} +} + +/** + * Get the name of the most recent plugin to be called in the + * call stack (or the plugin that owns the current page, if any). + * + * i.e., if the last plugin was in /mod/foobar/, get_plugin_name would return foo_bar. + * + * @deprecated 1.8 Use elgg_get_calling_plugin_id() + * + * @param boolean $mainfilename If set to true, this will instead determine the + *                              context from the main script filename called by + *                              the browser. Default = false. + * + * @return string|false Plugin name, or false if no plugin name was called + */ +function get_plugin_name($mainfilename = false) { +	elgg_deprecated_notice('get_plugin_name() is deprecated by elgg_get_calling_plugin_id()', 1.8); + +	return elgg_get_calling_plugin_id($mainfilename); +} + +/** + * Load and parse a plugin manifest from a plugin XML file. + * + * @example plugins/manifest.xml Example 1.8-style manifest file. + * + * @deprecated 1.8 Use ElggPlugin->getManifest() + * + * @param string $plugin Plugin name. + * @return array of values + */ +function load_plugin_manifest($plugin) { +	elgg_deprecated_notice('load_plugin_manifest() is deprecated by ElggPlugin->getManifest()', 1.8); + +	$xml_file = elgg_get_plugins_path() . "$plugin/manifest.xml"; + +	try { +		$manifest = new ElggPluginManifest($xml_file, $plugin); +	} catch(Exception $e) { +		return false; +	} + +	return $manifest->getManifest(); +} + +/** + * This function checks a plugin manifest 'elgg_version' value against the current install + * returning TRUE if the elgg_version is >= the current install's version. + * + * @deprecated 1.8 Use ElggPlugin->canActivate() + * + * @param string $manifest_elgg_version_string The build version (eg 2009010201). + * @return bool + */ +function check_plugin_compatibility($manifest_elgg_version_string) { +	elgg_deprecated_notice('check_plugin_compatibility() is deprecated by ElggPlugin->canActivate()', 1.8); + +	$version = get_version(); + +	if (strpos($manifest_elgg_version_string, '.') === false) { +		// Using version +		$req_version = (int)$manifest_elgg_version_string; + +		return ($version >= $req_version); +	} + +	return false; +} + +/** + * Shorthand function for finding the plugin settings. + * + * @deprecated 1.8 Use elgg_get_calling_plugin_entity() or elgg_get_plugin_from_id() + * + * @param string $plugin_id Optional plugin id, if not specified + *                          then it is detected from where you are calling. + * + * @return mixed + */ +function find_plugin_settings($plugin_id = null) { +	elgg_deprecated_notice('find_plugin_setting() is deprecated by elgg_get_calling_plugin_entity() or elgg_get_plugin_from_id()', 1.8); +	if ($plugin_id) { +		return elgg_get_plugin_from_id($plugin_id); +	} else { +		return elgg_get_calling_plugin_entity(); +	} +} + +/** + * Return an array of installed plugins. + * + * @deprecated 1.8 use elgg_get_plugins() + * + * @param string $status any|enabled|disabled + * @return array + */ +function get_installed_plugins($status = 'all') { +	global $CONFIG; + +	elgg_deprecated_notice('get_installed_plugins() was deprecated by elgg_get_plugins()', 1.8); + +	$plugins = elgg_get_plugins($status); + +	if (!$plugins) { +		return array(); +	} + +	$installed_plugins = array(); + +	foreach ($plugins as $plugin) { +		if (!$plugin->isValid()) { +			continue; +		} + +		$include = true; + +		if ($status == 'enabled' && !$plugin->isActive()) { +			$include = false; +		} elseif ($status == 'disabled' && $plugin->isActive()) { +			$include = true; +		} + +		if ($include) { +			$installed_plugins[$plugin->getID()] = array( +				'active' => $plugin->isActive(), +				'manifest' => $plugin->getManifest()->getManifest() +			); +		} +	} + +	return $installed_plugins; +} + +/** + * Enable a plugin for a site (default current site) + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * 		elgg_regenerate_simplecache(); + *		elgg_reset_system_cache(); + * + * @deprecated 1.8 Use ElggPlugin->activate() + * + * @param string $plugin    The plugin name. + * @param int    $site_guid The site id, if not specified then this is detected. + * + * @return array + * @throws InvalidClassException + */ +function enable_plugin($plugin, $site_guid = null) { +	elgg_deprecated_notice('enable_plugin() was deprecated by ElggPlugin->activate()', 1.8); + +	$plugin = sanitise_string($plugin); + +	$site_guid = (int) $site_guid; +	if (!$site_guid) { +		$site = get_config('site'); +		$site_guid = $site->guid; +	} + +	try { +		$plugin = new ElggPlugin($plugin); +	} catch(Exception $e) { +		return false; +	} + +	if (!$plugin->canActivate($site_guid)) { +		return false; +	} + +	return $plugin->activate($site_guid); +} + +/** + * Disable a plugin for a site (default current site) + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * 		elgg_regenerate_simplecache(); + *		elgg_reset_system_cache(); + * + * @deprecated 1.8 Use ElggPlugin->deactivate() + * + * @param string $plugin    The plugin name. + * @param int    $site_guid The site id, if not specified then this is detected. + * + * @return bool + * @throws InvalidClassException + */ +function disable_plugin($plugin, $site_guid = 0) { +	elgg_deprecated_notice('disable_plugin() was deprecated by ElggPlugin->deactivate()', 1.8); + +	$plugin = sanitise_string($plugin); + +	$site_guid = (int) $site_guid; +	if (!$site_guid) { +		$site = get_config('site'); +		$site_guid = $site->guid; +	} + +	try { +		$plugin = new ElggPlugin($plugin); +	} catch(Exception $e) { +		return false; +	} + +	return $plugin->deactivate($site_guid); +} + +/** + * Return whether a plugin is enabled or not. + * + * @deprecated 1.8 Use elgg_is_active_plugin() + * + * @param string $plugin    The plugin name. + * @param int    $site_guid The site id, if not specified then this is detected. + * + * @return bool + */ +function is_plugin_enabled($plugin, $site_guid = 0) { +	elgg_deprecated_notice('is_plugin_enabled() was deprecated by elgg_is_active_plugin()', 1.8); +	return elgg_is_active_plugin($plugin, $site_guid); +} + +/** + * Get entities based on their private data. + * + * @param string  $name           The name of the setting + * @param string  $value          The value of the setting + * @param string  $type           The type of entity (eg "user", "object" etc) + * @param string  $subtype        The arbitrary subtype of the entity + * @param int     $owner_guid     The GUID of the owning user + * @param string  $order_by       The field to order by; by default, time_created desc + * @param int     $limit          The number of entities to return; 10 by default + * @param int     $offset         The indexing offset, 0 by default + * @param boolean $count          Return a count of entities + * @param int     $site_guid      The site to get entities for. 0 for current, -1 for any + * @param mixed   $container_guid The container(s) GUIDs + * + * @return array A list of entities. + * @deprecated 1.8 Use elgg_get_entities_from_private_settings() + */ +function get_entities_from_private_setting($name = "", $value = "", $type = "", $subtype = "", +$owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, +$container_guid = null) { +	elgg_deprecated_notice('get_entities_from_private_setting() was deprecated by elgg_get_entities_from_private_setting()!', 1.8); + +	$options = array(); + +	$options['private_setting_name'] = $name; +	$options['private_setting_value'] = $value; + +	// set container_guid to owner_guid to emulate old functionality +	if ($owner_guid != "") { +		if (is_null($container_guid)) { +			$container_guid = $owner_guid; +		} +	} + +	if ($type) { +		$options['types'] = $type; +	} + +	if ($subtype) { +		$options['subtypes'] = $subtype; +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	if ($container_guid) { +		if (is_array($container_guid)) { +			$options['container_guids'] = $container_guid; +		} else { +			$options['container_guid'] = $container_guid; +		} +	} + +	$options['limit'] = $limit; + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($order_by) { +		$options['order_by']; +	} + +	if ($site_guid) { +		$options['site_guid']; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	return elgg_get_entities_from_private_settings($options); +} + +/** + * Get entities based on their private data by multiple keys. + * + * @param string $name           The name of the setting + * @param mixed  $type           Entity type + * @param string $subtype        Entity subtype + * @param int    $owner_guid     The GUID of the owning user + * @param string $order_by       The field to order by; by default, time_created desc + * @param int    $limit          The number of entities to return; 10 by default + * @param int    $offset         The indexing offset, 0 by default + * @param bool   $count          Count entities + * @param int    $site_guid      Site GUID. 0 for current, -1 for any. + * @param mixed  $container_guid Container GUID + * + * @return array A list of entities. + * @deprecated 1.8 Use elgg_get_entities_from_private_settings() + */ +function get_entities_from_private_setting_multi(array $name, $type = "", $subtype = "", +$owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, +$site_guid = 0, $container_guid = null) { + +	elgg_deprecated_notice('get_entities_from_private_setting_multi() was deprecated by elgg_get_entities_from_private_settings()!', 1.8); + +	$options = array(); + +	$pairs = array(); +	foreach ($name as $setting_name => $setting_value) { +		$pairs[] = array('name' => $setting_name, 'value' => $setting_value); +	} +	$options['private_setting_name_value_pairs'] = $pairs; + +	// set container_guid to owner_guid to emulate old functionality +	if ($owner_guid != "") { +		if (is_null($container_guid)) { +			$container_guid = $owner_guid; +		} +	} + +	if ($type) { +		$options['types'] = $type; +	} + +	if ($subtype) { +		$options['subtypes'] = $subtype; +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	if ($container_guid) { +		if (is_array($container_guid)) { +			$options['container_guids'] = $container_guid; +		} else { +			$options['container_guid'] = $container_guid; +		} +	} + +	$options['limit'] = $limit; + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($order_by) { +		$options['order_by']; +	} + +	if ($site_guid) { +		$options['site_guid']; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	return elgg_get_entities_from_private_settings($options); +} + +/** + * Returns a viewable list of entities by relationship + * + * @see elgg_view_entity_list + * + * @deprecated 1.8 Use elgg_list_entities_from_relationship() + * + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Reverse the normal function of the query to instead say "give me all entities for whome $relationship_guid is a $relationship of" + * @param string $type The type of entity (eg 'object') + * @param string $subtype The entity subtype + * @param int $owner_guid The owner (default: all) + * @param int $limit The number of entities to display on a page + * @param true|false $fullview Whether or not to display the full view (default: true) + * @param true|false $viewtypetoggle Whether or not to allow gallery view + * @param true|false $pagination Whether to display pagination (default: true) + * @param bool $order_by SQL order by clause + * @return string The viewable list of entities + */ +function list_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = ELGG_ENTITIES_ANY_VALUE, +$subtype = ELGG_ENTITIES_ANY_VALUE, $owner_guid = 0, $limit = 10, +$fullview = true, $listtypetoggle = false, $pagination = true, $order_by = '') { + +	elgg_deprecated_notice("list_entities_from_relationship was deprecated by elgg_list_entities_from_relationship()!", 1.8); +	return elgg_list_entities_from_relationship(array( +		'relationship' => $relationship, +		'relationship_guid' => $relationship_guid, +		'inverse_relationship' => $inverse_relationship, +		'type' => $type, +		'subtype' => $subtype, +		'owner_guid' => $owner_guid, +		'order_by' => $order_by, +		'limit' => $limit, +		'full_view' => $fullview, +		'list_type_toggle' => $listtypetoggle, +		'pagination' => $pagination, +	)); +} + +/** + * Gets the number of entities by a the number of entities related to them in a particular way. + * This is a good way to get out the users with the most friends, or the groups with the + * most members. + * + * @deprecated 1.8 Use elgg_get_entities_from_relationship_count() + * + * @param string $relationship         The relationship eg "friends_of" + * @param bool   $inverse_relationship Inverse relationship owners + * @param string $type                 The type of entity (default: all) + * @param string $subtype              The entity subtype (default: all) + * @param int    $owner_guid           The owner of the entities (default: none) + * @param int    $limit                Limit + * @param int    $offset               Offset + * @param bool   $count                Return a count instead of entities + * @param int    $site_guid            Site GUID + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + */ +function get_entities_by_relationship_count($relationship, $inverse_relationship = true, $type = "", +$subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { +	elgg_deprecated_notice('get_entities_by_relationship_count() is deprecated by elgg_get_entities_from_relationship_count()', 1.8); + +	$options = array(); + +	$options['relationship'] = $relationship; + +	// this used to default to true, which is wrong. +	// flip it for the new function +	$options['inverse_relationship'] = !$inverse_relationship; + +	if ($type) { +		$options['types'] = $type; +	} + +	if ($subtype) { +		$options['subtypes'] = $subtype; +	} + +	if ($owner_guid) { +		$options['owner_guid'] = $owner_guid; +	} + +	$options['limit'] = $limit; + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($site_guid) { +		$options['site_guid']; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	return elgg_get_entities_from_relationship_count($options); +} + +/** + * Displays a human-readable list of entities + * + * @deprecated 1.8 Use elgg_list_entities_from_relationship_count() + * + * @param string $relationship         The relationship eg "friends_of" + * @param bool   $inverse_relationship Inverse relationship owners + * @param string $type                 The type of entity (eg 'object') + * @param string $subtype              The entity subtype + * @param int    $owner_guid           The owner (default: all) + * @param int    $limit                The number of entities to display on a page + * @param bool   $fullview             Whether or not to display the full view (default: true) + * @param bool   $listtypetoggle       Whether or not to allow gallery view + * @param bool   $pagination           Whether to display pagination (default: true) + * + * @return string The viewable list of entities + */ +function list_entities_by_relationship_count($relationship, $inverse_relationship = true, +$type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, +$listtypetoggle = false, $pagination = true) { + +	elgg_deprecated_notice('list_entities_by_relationship_count() was deprecated by elgg_list_entities_from_relationship_count()', 1.8); + +	$options = array(); + +	$options['relationship'] = $relationship; + +	// this used to default to true, which is wrong. +	// flip it for the new function +	$options['inverse_relationship'] = !$inverse_relationship; + +	if ($type) { +		$options['types'] = $type; +	} + +	if ($subtype) { +		$options['subtypes'] = $subtype; +	} + +	if ($owner_guid) { +		$options['owner_guid'] = $owner_guid; +	} + +	$options['limit'] = $limit; + +	$options['full_view'] = $fullview; + +	return elgg_list_entities_from_relationship_count($options); +} + +/** + * Gets the number of entities by a the number of entities related to + * them in a particular way also constrained by metadata. + * + * @deprecated 1.8 Use elgg_get_entities_from_relationship() + * + * @param string $relationship         The relationship eg "friends_of" + * @param int    $relationship_guid    The guid of the entity to use query + * @param bool   $inverse_relationship Inverse relationship owner + * @param String $meta_name            The metadata name + * @param String $meta_value           The metadata value + * @param string $type                 The type of entity (default: all) + * @param string $subtype              The entity subtype (default: all) + * @param int    $owner_guid           The owner of the entities (default: none) + * @param int    $limit                Limit + * @param int    $offset               Offset + * @param bool   $count                Return a count instead of entities + * @param int    $site_guid            Site GUID + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + */ +function get_entities_from_relationships_and_meta($relationship, $relationship_guid, +$inverse_relationship = false, $meta_name = "", $meta_value = "", $type = "", +$subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + +	elgg_deprecated_notice('get_entities_from_relationship_and_meta() was deprecated by elgg_get_entities_from_relationship()!', 1.7); + +	$options = array(); + +	$options['relationship'] = $relationship; +	$options['relationship_guid'] = $relationship_guid; +	$options['inverse_relationship'] = $inverse_relationship; + +	if ($meta_value) { +		$options['values'] = $meta_value; +	} + +	if ($entity_type) { +		$options['types'] = $entity_type; +	} + +	if ($type) { +		$options['types'] = $type; +	} + +	if ($subtype) { +		$options['subtypes'] = $subtype; +	} + +	if ($owner_guid) { +		$options['owner_guid'] = $owner_guid; +	} + +	if ($limit) { +		$options['limit'] = $limit; +	} + +	if ($offset) { +		$options['offset'] = $offset; +	} + +	if ($order_by) { +		$options['order_by']; +	} + +	if ($site_guid) { +		$options['site_guid']; +	} + +	if ($count) { +		$options['count'] = $count; +	} + +	return elgg_get_entities_from_relationship($options); +} + + +/** + * Retrieves items from the river. All parameters are optional. + * + * @param int|array $subject_guid         Acting entity to restrict to. Default: all + * @param int|array $object_guid          Entity being acted on to restrict to. Default: all + * @param string    $subject_relationship If set to a relationship type, this will use + * 	                                      $subject_guid as the starting point and set the + *                                        subjects to be all users this + *                                        entity has this relationship with (eg 'friend'). + *                                        Default: blank + * @param string    $type                 The type of entity to restrict to. Default: all + * @param string    $subtype              The subtype of entity to restrict to. Default: all + * @param string    $action_type          The type of river action to restrict to. Default: all + * @param int       $limit                The number of items to retrieve. Default: 20 + * @param int       $offset               The page offset. Default: 0 + * @param int       $posted_min           The minimum time period to look at. Default: none + * @param int       $posted_max           The maximum time period to look at. Default: none + * + * @return array|false Depending on success + * @deprecated 1.8 Use elgg_get_river() + */ +function get_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', +$type = '',	$subtype = '', $action_type = '', $limit = 20, $offset = 0, $posted_min = 0, +$posted_max = 0) { +	elgg_deprecated_notice("get_river_items deprecated by elgg_get_river", 1.8); + +	$options = array(); + +	if ($subject_guid) { +		$options['subject_guid'] = $subject_guid; +	} + +	if ($object_guid) { +		$options['object_guid'] = $object_guid; +	} + +	if ($subject_relationship) { +		$options['relationship'] = $subject_relationship; +		unset($options['subject_guid']); +		$options['relationship_guid'] = $subject_guid; +	} + +	if ($type) { +		$options['type'] = $type; +	} + +	if ($subtype) { +		$options['subtype'] = $subtype; +	} + +	if ($action_type) { +		$options['action_type'] = $action_type; +	} + +	$options['limit'] = $limit; +	$options['offset'] = $offset; + +	if ($posted_min) { +		$options['posted_time_lower'] = $posted_min; +	} + +	if ($posted_max) { +		$options['posted_time_upper'] = $posted_max; +	} + +	return elgg_get_river($options); +} + +/** + * Returns a human-readable version of the river. + * + * @param int|array $subject_guid         Acting entity to restrict to. Default: all + * @param int|array $object_guid          Entity being acted on to restrict to. Default: all + * @param string    $subject_relationship If set to a relationship type, this will use + * 	                                      $subject_guid as the starting point and set + *                                        the subjects to be all users this entity has this + *                                        relationship with (eg 'friend'). Default: blank + * @param string    $type                 The type of entity to restrict to. Default: all + * @param string    $subtype              The subtype of entity to restrict to. Default: all + * @param string    $action_type          The type of river action to restrict to. Default: all + * @param int       $limit                The number of items to retrieve. Default: 20 + * @param int       $posted_min           The minimum time period to look at. Default: none + * @param int       $posted_max           The maximum time period to look at. Default: none + * @param bool      $pagination           Show pagination? + * + * @return string Human-readable river. + * @deprecated 1.8 Use elgg_list_river() + */ +function elgg_view_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', +$type = '', $subtype = '', $action_type = '', $limit = 20, $posted_min = 0, +$posted_max = 0, $pagination = true) { +	elgg_deprecated_notice("elgg_view_river_items deprecated for elgg_list_river", 1.8); + +	$river_items = get_river_items($subject_guid, $object_guid, $subject_relationship, +			$type, $subtype, $action_type, $limit + 1, $posted_min, $posted_max); + +	// Get input from outside world and sanitise it +	$offset = (int) get_input('offset', 0); + +	// view them +	$params = array( +		'items' => $river_items, +		'count' => count($river_items), +		'offset' => $offset, +		'limit' => $limit, +		'pagination' => $pagination, +		'list-class' => 'elgg-list-river', +	); + +	return elgg_view('page/components/list', $params); +} + +/** + * Construct and execute the query required for the activity stream. + * + * @deprecated 1.8 This is outdated and uses the systemlog table instead of the river table. + *                 Don't use it. + */ +function get_activity_stream_data($limit = 10, $offset = 0, $type = "", $subtype = "", +$owner_guid = "", $owner_relationship = "") { +	elgg_deprecated_notice("get_activity_stream_data was deprecated", 1.8); + +	global $CONFIG; + +	$limit = (int)$limit; +	$offset = (int)$offset; + +	if ($type) { +		if (!is_array($type)) { +			$type = array(sanitise_string($type)); +		} else { +			foreach ($type as $k => $v) { +				$type[$k] = sanitise_string($v); +			} +		} +	} + +	if ($subtype) { +		if (!is_array($subtype)) { +			$subtype = array(sanitise_string($subtype)); +		} else { +			foreach ($subtype as $k => $v) { +				$subtype[$k] = sanitise_string($v); +			} +		} +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			foreach ($owner_guid as $k => $v) { +				$owner_guid[$k] = (int)$v; +			} +		} else { +			$owner_guid = array((int)$owner_guid); +		} +	} + +	$owner_relationship = sanitise_string($owner_relationship); + +	// Get a list of possible views +	$activity_events = array(); +	$activity_views = array_merge(elgg_view_tree('activity', 'default'), +		elgg_view_tree('river', 'default')); + +	$done = array(); + +	foreach ($activity_views as $view) { +		$fragments = explode('/', $view); +		$tmp = explode('/', $view, 2); +		$tmp = $tmp[1]; + +		if ((isset($fragments[0])) && (($fragments[0] == 'river') || ($fragments[0] == 'activity')) +			&& (!in_array($tmp, $done))) { + +			if (isset($fragments[1])) { +				$f = array(); +				for ($n = 1; $n < count($fragments); $n++) { +					$val = sanitise_string($fragments[$n]); +					switch($n) { +						case 1: $key = 'type'; break; +						case 2: $key = 'subtype'; break; +						case 3: $key = 'event'; break; +					} +					$f[$key] = $val; +				} + +				// Filter result based on parameters +				$add = true; +				if ($type) { +					if (!in_array($f['type'], $type)) { +						$add = false; +					} +				} +				if (($add) && ($subtype)) { +					if (!in_array($f['subtype'], $subtype)) { +						$add = false; +					} +				} +				if (($add) && ($event)) { +					if (!in_array($f['event'], $event)) { +						$add = false; +					} +				} + +				if ($add) { +					$activity_events[] = $f; +				} +			} + +			$done[] = $tmp; +		} +	} + +	$n = 0; +	foreach ($activity_events as $details) { +		// Get what we're talking about +		if ($details['subtype'] == 'default') { +			$details['subtype'] = ''; +		} + +		if (($details['type']) && ($details['event'])) { +			if ($n > 0) { +				$obj_query .= " or "; +			} + +			$access = ""; +			if ($details['type'] != 'relationship') { +				$access = " and " . get_access_sql_suffix('sl'); +			} + +			$obj_query .= "( sl.object_type='{$details['type']}' +				AND sl.object_subtype='{$details['subtype']}' +				AND sl.event='{$details['event']}' $access )"; + +			$n++; +		} +	} + +	// User +	if ((count($owner_guid)) &&  ($owner_guid[0] != 0)) { +		$user = " and sl.performed_by_guid in (" . implode(',', $owner_guid) . ")"; + +		if ($owner_relationship) { +			$friendsarray = ""; +			if ($friends = elgg_get_entities_from_relationship(array( +				'relationship' => $owner_relationship, +				'relationship_guid' => $owner_guid[0], +				'inverse_relationship' => FALSE, +				'type' => 'user', +				'subtype' => $subtype, +				'limit' => false)) +			) { + +				$friendsarray = array(); +				foreach ($friends as $friend) { +					$friendsarray[] = $friend->getGUID(); +				} + +				$user = " and sl.performed_by_guid in (" . implode(',', $friendsarray) . ")"; +			} +		} +	} + +	$query = "SELECT sl.* FROM {$CONFIG->dbprefix}system_log sl +		WHERE 1 $user AND ($obj_query) +		ORDER BY sl.time_created desc limit $offset, $limit"; +	return get_data($query); +} + +/** + * Perform standard authentication with a given username and password. + * Returns an ElggUser object for use with login. + * + * @see login + * + * @param string $username The username, optionally (for standard logins) + * @param string $password The password, optionally (for standard logins) + * + * @return ElggUser|false The authenticated user object, or false on failure. + * + * @deprecated 1.8 Use elgg_authenticate + */ +function authenticate($username, $password) { +	elgg_deprecated_notice('authenticate() has been deprecated for elgg_authenticate()', 1.8); +	$pam = new ElggPAM('user'); +	$credentials = array('username' => $username, 'password' => $password); +	$result = $pam->authenticate($credentials); +	if ($result) { +		return get_user_by_username($username); +	} +	return false; +} + + +/** + * Get the members of a site. + * + * @param int $site_guid Site GUID + * @param int $limit     User GUID + * @param int $offset    Offset + * + * @return mixed + * @deprecated 1.8 Use ElggSite::getMembers() + */ +function get_site_members($site_guid, $limit = 10, $offset = 0) { +	elgg_deprecated_notice("get_site_members() deprecated. +		Use ElggSite::getMembers()", 1.8); + +	$site = get_entity($site_guid); +	if ($site) { +		return $site->getMembers($limit, $offset); +	} + +	return false; +} + +/** + * Display a list of site members + * + * @param int  $site_guid The GUID of the site + * @param int  $limit     The number of members to display on a page + * @param bool $fullview  Whether or not to display the full view (default: true) + * + * @return string A displayable list of members + * @deprecated 1.8 Use ElggSite::listMembers() + */ +function list_site_members($site_guid, $limit = 10, $fullview = true) { +	elgg_deprecated_notice("list_site_members() deprecated. +		Use ElggSite::listMembers()", 1.8); + +	$options = array( +		'limit' => $limit, +		'full_view' => $full_view, +	); + +	$site = get_entity($site_guid); +	if ($site) { +		return $site->listMembers($options); +	} + +	return ''; +} + + +/** + * Add a collection to a site. + * + * @param int $site_guid       Site GUID + * @param int $collection_guid Collection GUID + * + * @return mixed + * @deprecated 1.8 Don't use this. + */ +function add_site_collection($site_guid, $collection_guid) { +	elgg_deprecated_notice("add_site_collection has been deprecated", 1.8); +	global $CONFIG; + +	$site_guid = (int)$site_guid; +	$collection_guid = (int)$collection_guid; + +	return add_entity_relationship($collection_guid, "member_of_site", $site_guid); +} + +/** + * Remove a collection from a site. + * + * @param int $site_guid       Site GUID + * @param int $collection_guid Collection GUID + * + * @return mixed + * @deprecated 1.8 Don't use this. + */ +function remove_site_collection($site_guid, $collection_guid) { +	elgg_deprecated_notice("remove_site_collection has been deprecated", 1.8); +	$site_guid = (int)$site_guid; +	$collection_guid = (int)$collection_guid; + +	return remove_entity_relationship($collection_guid, "member_of_site", $site_guid); +} + +/** + * Get the collections belonging to a site. + * + * @param int    $site_guid Site GUID + * @param string $subtype   Subtype + * @param int    $limit     Limit + * @param int    $offset    Offset + * + * @return mixed + * @deprecated 1.8 Don't use this. + */ +function get_site_collections($site_guid, $subtype = "", $limit = 10, $offset = 0) { +	elgg_deprecated_notice("get_site_collections has been deprecated", 1.8); +	$site_guid = (int)$site_guid; +	$subtype = sanitise_string($subtype); +	$limit = (int)$limit; +	$offset = (int)$offset; + +	// collection isn't a valid type.  This won't work. +	return elgg_get_entities_from_relationship(array( +		'relationship' => 'member_of_site', +		'relationship_guid' => $site_guid, +		'inverse_relationship' => TRUE, +		'type' => 'collection', +		'subtype' => $subtype, +		'limit' => $limit, +		'offset' => $offset +	)); +} + +/** + * Get an array of tags with weights for use with the output/tagcloud view. + * + * @deprecated 1.8  Use elgg_get_tags(). + * + * @param int    $threshold      Get the threshold of minimum number of each tags to + *                               bother with (ie only show tags where there are more + *                               than $threshold occurances) + * @param int    $limit          Number of tags to return + * @param string $metadata_name  Optionally, the name of the field you want to grab for + * @param string $entity_type    Optionally, the entity type ('object' etc) + * @param string $entity_subtype The entity subtype, optionally + * @param int    $owner_guid     The GUID of the tags owner, optionally + * @param int    $site_guid      Optionally, the site to restrict to (default is the current site) + * @param int    $start_ts       Optionally specify a start timestamp for tags used to + *                               generate cloud. + * @param int    $end_ts         Optionally specify an end timestamp for tags used to generate cloud + * + * @return array|false Array of objects with ->tag and ->total values, or false on failure + */ +function get_tags($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", +$entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { + +	elgg_deprecated_notice('get_tags() has been replaced by elgg_get_tags()', 1.8); + +	if (is_array($metadata_name)) { +		return false; +	} + +	$options = array(); +	if ($metadata_name === '') { +		$options['tag_names'] = array(); +	} else { +		$options['tag_names'] = array($metadata_name); +	} + +	$options['threshold'] = $threshold; +	$options['limit'] = $limit; + +	// rewrite owner_guid to container_guid to emulate old functionality +	$container_guid = $owner_guid; +	if ($container_guid) { +		$options['container_guids'] = $container_guid; +	} + +	if ($entity_type) { +		$options['type'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtype'] = $entity_subtype; +	} + +	if ($site_guid != -1) { +		$options['site_guids'] = $site_guid; +	} + +	if ($end_ts) { +		$options['created_time_upper'] = $end_ts; +	} + +	if ($start_ts) { +		$options['created_time_lower'] = $start_ts; +	} + +	$r = elgg_get_tags($options); +	return $r; +} + +/** + * Loads and displays a tagcloud given particular criteria. + * + * @deprecated 1.8 use elgg_view_tagcloud() + * + * @param int    $threshold      Get the threshold of minimum number of each tags + *                               to bother with (ie only show tags where there are + *                               more than $threshold occurances) + * @param int    $limit          Number of tags to return + * @param string $metadata_name  Optionally, the name of the field you want to grab for + * @param string $entity_type    Optionally, the entity type ('object' etc) + * @param string $entity_subtype The entity subtype, optionally + * @param int    $owner_guid     The GUID of the tags owner, optionally + * @param int    $site_guid      Optionally, the site to restrict to (default is the current site) + * @param int    $start_ts       Optionally specify a start timestamp for tags used to + *                               generate cloud. + * @param int    $end_ts         Optionally specify an end timestamp for tags used to generate + *                               cloud. + * + * @return string The HTML (or other, depending on view type) of the tagcloud. + */ +function display_tagcloud($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", +$entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { + +	elgg_deprecated_notice('display_tagcloud() was deprecated by elgg_view_tagcloud()!', 1.8); + +	$tags = get_tags($threshold, $limit, $metadata_name, $entity_type, +		$entity_subtype, $owner_guid, $site_guid, $start_ts, $end_ts); + +	return elgg_view('output/tagcloud', array( +		'value' => $tags, +		'type' => $entity_type, +		'subtype' => $entity_subtype, +	)); +} + + +/** + * Obtains a list of objects owned by a user + * + * @param int    $user_guid The GUID of the owning user + * @param string $subtype   Optionally, the subtype of objects + * @param int    $limit     The number of results to return (default 10) + * @param int    $offset    Indexing offset, if any + * @param int    $timelower The earliest time the entity can have been created. Default: all + * @param int    $timeupper The latest time the entity can have been created. Default: all + * + * @return false|array An array of ElggObjects or false, depending on success + * @deprecated 1.8 Use elgg_get_entities() instead + */ +function get_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$offset = 0, $timelower = 0, $timeupper = 0) { +	elgg_deprecated_notice("get_user_objects() was deprecated in favor of elgg_get_entities()", 1.8); +	$ntt = elgg_get_entities(array( +		'type' => 'object', +		'subtype' => $subtype, +		'owner_guid' => $user_guid, +		'limit' => $limit, +		'offset' => $offset, +		'container_guid' => $user_guid, +		'created_time_lower' => $timelower, +		'created_time_upper' => $timeupper +	)); +	return $ntt; +} + +/** + * Counts the objects (optionally of a particular subtype) owned by a user + * + * @param int    $user_guid The GUID of the owning user + * @param string $subtype   Optionally, the subtype of objects + * @param int    $timelower The earliest time the entity can have been created. Default: all + * @param int    $timeupper The latest time the entity can have been created. Default: all + * + * @return int The number of objects the user owns (of this subtype) + * @deprecated 1.8 Use elgg_get_entities() instead + */ +function count_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $timelower = 0, +$timeupper = 0) { +	elgg_deprecated_notice("count_user_objects() was deprecated in favor of elgg_get_entities()", 1.8); +	$total = elgg_get_entities(array( +		'type' => 'object', +		'subtype' => $subtype, +		'owner_guid' => $user_guid, +		'count' => TRUE, +		'container_guid' => $user_guid, +		'created_time_lower' => $timelower, +		'created_time_upper' => $timeupper +	)); +	return $total; +} + +/** + * Displays a list of user objects of a particular subtype, with navigation. + * + * @see elgg_view_entity_list + * + * @param int    $user_guid      The GUID of the user + * @param string $subtype        The object subtype + * @param int    $limit          The number of entities to display on a page + * @param bool   $fullview       Whether or not to display the full view (default: true) + * @param bool   $listtypetoggle Whether or not to allow gallery view (default: true) + * @param bool   $pagination     Whether to display pagination (default: true) + * @param int    $timelower      The earliest time the entity can have been created. Default: all + * @param int    $timeupper      The latest time the entity can have been created. Default: all + * + * @return string The list in a form suitable to display + * @deprecated 1.8 Use elgg_list_entities() instead + */ +function list_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$fullview = true, $listtypetoggle = true, $pagination = true, $timelower = 0, $timeupper = 0) { +	elgg_deprecated_notice("list_user_objects() was deprecated in favor of elgg_list_entities()", 1.8); + +	$offset = (int) get_input('offset'); +	$limit = (int) $limit; +	$count = (int) count_user_objects($user_guid, $subtype, $timelower, $timeupper); +	$entities = get_user_objects($user_guid, $subtype, $limit, $offset, $timelower, $timeupper); + +	return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, +		$pagination); +} + + +/** + * Get user objects by an array of metadata + * + * @param int    $user_guid The GUID of the owning user + * @param string $subtype   Optionally, the subtype of objects + * @param array  $metadata  An array of metadata + * @param int    $limit     The number of results to return (default 10) + * @param int    $offset    Indexing offset, if any + * + * @return false|array An array of ElggObjects or false, depending on success + * @deprecated 1.8 Use elgg_get_entities_from_metadata() instead + */ +function get_user_objects_by_metadata($user_guid, $subtype = "", $metadata = array(), +$limit = 0, $offset = 0) { +	elgg_deprecated_notice("get_user_objects_by_metadata() was deprecated in favor of elgg_get_entities_from_metadata()", 1.8); +	return get_entities_from_metadata_multi($metadata, "object", $subtype, $user_guid, +		$limit, $offset); +} + +/** + * Set the validation status for a user. + * + * @param bool   $status Validated (true) or false + * @param string $method Optional method to say how a user was validated + * @return bool + * @deprecated 1.8 Use elgg_set_user_validation_status() + */ +function set_user_validation_status($user_guid, $status, $method = '') { +	elgg_deprecated_notice("set_user_validation_status() is deprecated", 1.8); +	return elgg_set_user_validation_status($user_guid, $status, $method); +} + +/** + * Trigger an event requesting that a user guid be validated somehow - either by email address or some other way. + * + * This function invalidates any existing validation value. + * + * @param int $user_guid User's GUID + * @deprecated 1.8 Hook into the register, user plugin hook and request validation. + */ +function request_user_validation($user_guid) { +	elgg_deprecated_notice("request_user_validation() is deprecated. +		Plugins should register for the 'register, user' plugin hook", 1.8); +	$user = get_entity($user_guid); + +	if (($user) && ($user instanceof ElggUser)) { +		// invalidate any existing validations +		set_user_validation_status($user_guid, false); + +		// request validation +		trigger_elgg_event('validate', 'user', $user); +	} +} + +/** + * Register a user settings page with the admin panel. + * This function extends the view "usersettings/main" with the provided view. + * This view should provide a description and either a control or a link to. + * + * Usage: + * 	- To add a control to the main admin panel then extend usersettings/main + *  - To add a control to a new page create a page which renders a view + *    usersettings/subpage (where subpage is your new page - + *    nb. some pages already exist that you can extend), extend the main view + *    to point to it, and add controls to your new view. + * + * At the moment this is essentially a wrapper around elgg_extend_view(). + * + * @param string $new_settings_view The view associated with the control you're adding + * @param string $view              The view to extend, by default this is 'usersettings/main'. + * @param int    $priority          Optional priority to govern the appearance in the list. + * + * @return bool + * @deprecated 1.8 Extend one of the views in core/settings + */ +function extend_elgg_settings_page($new_settings_view, $view = 'usersettings/main', +$priority = 500) { +	// see views: /core/settings +	elgg_deprecated_notice("extend_elgg_settings_page has been deprecated. Extend one of the settings views instead", 1.8); + +	return elgg_extend_view($view, $new_settings_view, $priority); +} + +/** + * Returns a representation of a full 'page' (which might be an HTML page, + * RSS file, etc, depending on the current viewtype) + * + * @param string $title + * @param string $body + * @return string + * + * @deprecated 1.8 Use elgg_view_page() + */ +function page_draw($title, $body, $sidebar = "") { +	elgg_deprecated_notice("page_draw() was deprecated in favor of elgg_view_page() in 1.8.", 1.8); + +	$vars = array( +		'sidebar' => $sidebar +	); +	echo elgg_view_page($title, $body, 'default', $vars); +} + +/** + * Wrapper function to display search listings. + * + * @param string $icon The icon for the listing + * @param string $info Any information that needs to be displayed. + * + * @return string The HTML (etc) representing the listing + * @deprecated 1.8 use elgg_view_image_block() + */ +function elgg_view_listing($icon, $info) { +	elgg_deprecated_notice('elgg_view_listing deprecated by elgg_view_image_block', 1.8); +	return elgg_view('page/components/image_block', array('image' => $icon, 'body' => $info)); +} + +/** + * Return the icon URL for an entity. + * + * @tip Can be overridden by registering a plugin hook for entity:icon:url, $entity_type. + * + * @internal This is passed an entity rather than a guid to handle non-created entities. + * + * @param ElggEntity $entity The entity + * @param string     $size   Icon size + * + * @return string URL to the entity icon. + * @deprecated 1.8 Use $entity->getIconURL() + */ +function get_entity_icon_url(ElggEntity $entity, $size = 'medium') { +	elgg_deprecated_notice("get_entity_icon_url() deprecated for getIconURL()", 1.8); +	global $CONFIG; + +	$size = sanitise_string($size); +	switch (strtolower($size)) { +		case 'master': +			$size = 'master'; +			break; + +		case 'large' : +			$size = 'large'; +			break; + +		case 'topbar' : +			$size = 'topbar'; +			break; + +		case 'tiny' : +			$size = 'tiny'; +			break; + +		case 'small' : +			$size = 'small'; +			break; + +		case 'medium' : +		default: +			$size = 'medium'; +	} + +	$url = false; + +	$viewtype = elgg_get_viewtype(); + +	// Step one, see if anyone knows how to render this in the current view +	$params = array('entity' => $entity, 'viewtype' => $viewtype, 'size' => $size); +	$url = elgg_trigger_plugin_hook('entity:icon:url', $entity->getType(), $params, $url); + +	// Fail, so use default +	if (!$url) { +		$type = $entity->getType(); +		$subtype = $entity->getSubtype(); + +		if (!empty($subtype)) { +			$overrideurl = elgg_view("icon/{$type}/{$subtype}/{$size}", array('entity' => $entity)); +			if (!empty($overrideurl)) { +				return $overrideurl; +			} +		} + +		$overrideurl = elgg_view("icon/{$type}/default/{$size}", array('entity' => $entity)); +		if (!empty($overrideurl)) { +			return $overrideurl; +		} + +		$url = "_graphics/icons/default/$size.png"; +	} + +	return elgg_normalize_url($url); +} + +/** + * Return the current logged in user, or NULL if no user is logged in. + * + * If no user can be found in the current session, a plugin + * hook - 'session:get' 'user' to give plugin authors another + * way to provide user details to the ACL system without touching the session. + * + * @deprecated 1.8 Use elgg_get_logged_in_user_entity() + * @return ElggUser|NULL + */ +function get_loggedin_user() { +	elgg_deprecated_notice('get_loggedin_user() is deprecated by elgg_get_logged_in_user_entity()', 1.8); +	return elgg_get_logged_in_user_entity(); +} + +/** + * Return the current logged in user by id. + * + * @deprecated 1.8 Use elgg_get_logged_in_user_guid() + * @see elgg_get_logged_in_user_entity() + * @return int + */ +function get_loggedin_userid() { +	elgg_deprecated_notice('get_loggedin_userid() is deprecated by elgg_get_logged_in_user_guid()', 1.8); +	return elgg_get_logged_in_user_guid(); +} + + +/** + * Returns whether or not the user is currently logged in + * + * @deprecated 1.8 Use elgg_is_logged_in(); + * @return bool + */ +function isloggedin() { +	elgg_deprecated_notice('isloggedin() is deprecated by elgg_is_logged_in()', 1.8); +	return elgg_is_logged_in(); +} + +/** + * Returns whether or not the user is currently logged in and that they are an admin user. + * + * @deprecated 1.8 Use elgg_is_admin_logged_in() + * @return bool + */ +function isadminloggedin() { +	elgg_deprecated_notice('isadminloggedin() is deprecated by elgg_is_admin_logged_in()', 1.8); +	return elgg_is_admin_logged_in(); +} + + +/** + * Loads plugins + * + * @deprecated 1.8 Use elgg_load_plugins() + * + * @return bool + */ +function load_plugins() { +	elgg_deprecated_notice('load_plugins() is deprecated by elgg_load_plugins()', 1.8); +	return elgg_load_plugins(); +} + +/** + * Find the plugin settings for a user. + * + * @param string $plugin_id Plugin name. + * @param int    $user_guid The guid who's settings to retrieve. + * + * @deprecated 1.8 Use elgg_get_all_plugin_user_settings() or ElggPlugin->getAllUserSettings() + * @return StdClass Object with all user settings. + */ +function find_plugin_usersettings($plugin_id = null, $user_guid = 0) { +	elgg_deprecated_notice('find_plugin_usersettings() is deprecated by elgg_get_all_plugin_user_settings()', 1.8); +	return elgg_get_all_plugin_user_settings($user_guid, $plugin_id, true); +} + +/** + * Set a user specific setting for a plugin. + * + * @param string $name      The name - note, can't be "title". + * @param mixed  $value     The value. + * @param int    $user_guid Optional user. + * @param string $plugin_id Optional plugin name, if not specified then it + *                          is detected from where you are calling from. + * + * @return bool + * @deprecated 1.8 Use elgg_set_plugin_user_setting() or ElggPlugin->setUserSetting() + */ +function set_plugin_usersetting($name, $value, $user_guid = 0, $plugin_id = "") { +	elgg_deprecated_notice('find_plugin_usersettings() is deprecated by elgg_get_all_plugin_user_settings()', 1.8); +	return elgg_set_plugin_user_setting($name, $value, $user_guid, $plugin_id); +} + +/** + * Clears a user-specific plugin setting + * + * @param str $name      Name of the plugin setting + * @param int $user_guid Defaults to logged in user + * @param str $plugin_id Defaults to contextual plugin name + * + * @deprecated 1.8 Use elgg_unset_plugin_user_setting or ElggPlugin->unsetUserSetting(). + * @return bool Success + */ +function clear_plugin_usersetting($name, $user_guid = 0, $plugin_id = '') { +	elgg_deprecated_notice('clear_plugin_usersetting() is deprecated by elgg_unset_plugin_usersetting()', 1.8); +	return elgg_unset_plugin_user_setting($name, $user_guid, $plugin_id); +} + +/** + * Get a user specific setting for a plugin. + * + * @param string $name      The name. + * @param int    $user_guid Guid of owning user + * @param string $plugin_id Optional plugin name, if not specified + *                          it is detected from where you are calling. + * + * @deprecated 1.8 Use elgg_get_plugin_user_setting() or ElggPlugin->getUserSetting() + * @return mixed + */ +function get_plugin_usersetting($name, $user_guid = 0, $plugin_id = "") { +	elgg_deprecated_notice('get_plugin_usersetting() is deprecated by elgg_get_plugin_user_setting()', 1.8); +	return elgg_get_plugin_user_setting($name, $user_guid, $plugin_id); +} + +/** + * Set a setting for a plugin. + * + * @param string $name      The name - note, can't be "title". + * @param mixed  $value     The value. + * @param string $plugin_id Optional plugin name, if not specified + *                          then it is detected from where you are calling from. + * + * @deprecated 1.8 Use elgg_set_plugin_setting() or ElggPlugin->setSetting() + * @return int|false + */ +function set_plugin_setting($name, $value, $plugin_id = null) { +	elgg_deprecated_notice('set_plugin_setting() is deprecated by elgg_set_plugin_setting()', 1.8); +	return elgg_set_plugin_setting($name, $value, $plugin_id); +} + +/** + * Get setting for a plugin. + * + * @param string $name      The name. + * @param string $plugin_id Optional plugin name, if not specified + *                          then it is detected from where you are calling from. + * + * @deprecated 1.8 Use elgg_get_plugin_setting() or ElggPlugin->getSetting() + * @return mixed + */ +function get_plugin_setting($name, $plugin_id = "") { +	elgg_deprecated_notice('get_plugin_setting() is deprecated by elgg_get_plugin_setting()', 1.8); +	return elgg_get_plugin_setting($name, $plugin_id); +} + +/** + * Clear a plugin setting. + * + * @param string $name      The name. + * @param string $plugin_id Optional plugin name, if not specified + *                          then it is detected from where you are calling from. + * + * @deprecated 1.8 Use elgg_unset_plugin_setting() or ElggPlugin->unsetSetting() + * @return bool + */ +function clear_plugin_setting($name, $plugin_id = "") { +	elgg_deprecated_notice('clear_plugin_setting() is deprecated by elgg_unset_plugin_setting()', 1.8); +	return elgg_unset_plugin_setting($name, $plugin_id); +} + +/** + * Unsets all plugin settings for a plugin. + * + * @param string $plugin_id Optional plugin name, if not specified + *                          then it is detected from where you are calling from. + * + * @return bool + * @deprecated 1.8 Use elgg_unset_all_plugin_settings() or ElggPlugin->unsetAllSettings() + * @since 1.7.0 + */ +function clear_all_plugin_settings($plugin_id = "") { +	elgg_deprecated_notice('clear_all_plugin_settings() is deprecated by elgg_unset_all_plugin_setting()', 1.8); +	return elgg_unset_all_plugin_settings($plugin_id); +} + + +/** + * Get a list of annotations for a given object/user/annotation type. + * + * @param int|array $entity_guid       GUID to return annotations of (falsey for any) + * @param string    $entity_type       Type of entity + * @param string    $entity_subtype    Subtype of entity + * @param string    $name              Name of annotation + * @param mixed     $value             Value of annotation + * @param int|array $owner_guid        Owner(s) of annotation + * @param int       $limit             Limit + * @param int       $offset            Offset + * @param string    $order_by          Order annotations by SQL + * @param int       $timelower         Lower time limit + * @param int       $timeupper         Upper time limit + * @param int       $entity_owner_guid Owner guid for the entity + * + * @return array + * @deprecated 1.8 Use elgg_get_annotations() + */ +function get_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", $timelower = 0, +$timeupper = 0, $entity_owner_guid = 0) { + +	elgg_deprecated_notice('get_annotations() is deprecated by elgg_get_annotations()', 1.8); +	$options = array(); + +	if ($entity_guid) { +		$options['guid'] = $entity_guid; +	} + +	if ($entity_type) { +		$options['type'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtype'] = $entity_subtype; +	} + +	if ($name) { +		$options['annotation_name'] = $name; +	} + +	if ($value) { +		$options['annotation_value'] = $value; +	} + +	if ($owner_guid) { +		$options['annotation_owner_guid'] = $owner_guid; +	} + +	$options['limit'] = $limit; +	$options['offset'] = $offset; + +	if ($order_by == 'desc') { +		$options['order_by'] = 'n_table.time_created desc'; +	} + +	if ($timelower) { +		$options['annotation_time_lower'] = $timelower; +	} + +	if ($timeupper) { +		$options['annotation_time_upper'] = $timeupper; +	} + +	if ($entity_owner_guid) { +		$options['owner_guid'] = $entity_owner_guid; +	} + +	return elgg_get_annotations($options); +} + + +/** + * Returns a human-readable list of annotations on a particular entity. + * + * @param int        $entity_guid The entity GUID + * @param string     $name        The name of the kind of annotation + * @param int        $limit       The number of annotations to display at once + * @param true|false $asc         Display annotations in ascending order. (Default: true) + * + * @return string HTML (etc) version of the annotation list + * @deprecated 1.8 Use elgg_list_annotations() + */ +function list_annotations($entity_guid, $name = "", $limit = 25, $asc = true) { +	elgg_deprecated_notice('list_annotations() is deprecated by elgg_list_annotations()', 1.8); + +	if ($asc) { +		$asc = "asc"; +	} else { +		$asc = "desc"; +	} + +	$options = array( +		'guid' => $entity_guid, +		'limit' => $limit, +		'order_by' => "n_table.time_created $asc" +	); + +	return elgg_list_annotations($options); +} + +/** + * Helper function to deprecate annotation calculation functions. Don't use. + * + * @param unknown_type $entity_guid + * @param unknown_type $entity_type + * @param unknown_type $entity_subtype + * @param unknown_type $name + * @param unknown_type $value + * @param unknown_type $value_type + * @param unknown_type $owner_guid + * @param unknown_type $timelower + * @param unknown_type $timeupper + * @param unknown_type $calculation + * @internal Don't use this at all. + * @deprecated 1.8 Use elgg_get_annotations() + */ +function elgg_deprecated_annotation_calculation($entity_guid = 0, $entity_type = "", $entity_subtype = "", +$name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, +$timeupper = 0, $calculation = '') { + +	$options = array('annotation_calculation' => $calculation); + +	if ($entity_guid) { +		$options['guid'] = $entity_guid; +	} + +	if ($entity_type) { +		$options['type'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtype'] = $entity_subtype; +	} + +	if ($name) { +		$options['annotation_name'] = $name; +	} + +	if ($value) { +		$options['annotation_value'] = $value; +	} + +	if ($owner_guid) { +		$options['annotation_owner_guid'] = $owner_guid; +	} + +	if ($order_by == 'desc') { +		$options['order_by'] = 'n_table.time_created desc'; +	} + +	if ($timelower) { +		$options['annotation_time_lower'] = $timelower; +	} + +	if ($timeupper) { +		$options['annotation_time_upper'] = $timeupper; +	} + +	return elgg_get_annotations($options); +} + +/** + * Count the number of annotations based on search parameters + * + * @param int    $entity_guid    Guid of Entity + * @param string $entity_type    Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name           Name of annotation + * @param string $value          Value of annotation + * @param string $value_type     Type of value + * @param int    $owner_guid     GUID of owner of annotation + * @param int    $timelower      Lower time limit + * @param int    $timeupper      Upper time limit + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'count' => true + * @return int + */ +function count_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", +$name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, +$timeupper = 0) { +	elgg_deprecated_notice('count_annotations() is deprecated by elgg_get_annotations() and passing "count" => true', 1.8); +	return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, +			$name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'count'); +} + +/** + * Return the sum of a given integer annotation. + * + * @param int    $entity_guid    Guid of Entity + * @param string $entity_type    Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name           Name of annotation + * @param string $value          Value of annotation + * @param string $value_type     Type of value + * @param int    $owner_guid     GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'sum' + * @return int + */ +function get_annotations_sum($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { +	elgg_deprecated_notice('get_annotations_sum() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "sum"', 1.8); + +	return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, +			$name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'sum'); +} + +/** + * Return the max of a given integer annotation. + * + * @param int    $entity_guid    Guid of Entity + * @param string $entity_type    Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name           Name of annotation + * @param string $value          Value of annotation + * @param string $value_type     Type of value + * @param int    $owner_guid     GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'max' + * @return int + */ +function get_annotations_max($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { +	elgg_deprecated_notice('get_annotations_max() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "max"', 1.8); + +	return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, +			$name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'max'); +} + + +/** + * Return the minumum of a given integer annotation. + * + * @param int    $entity_guid    Guid of Entity + * @param string $entity_type    Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name           Name of annotation + * @param string $value          Value of annotation + * @param string $value_type     Type of value + * @param int    $owner_guid     GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'min' + * @return int + */ +function get_annotations_min($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { +	elgg_deprecated_notice('get_annotations_min() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "min"', 1.8); + +	return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, +			$name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'min'); +} + + +/** + * Return the average of a given integer annotation. + * + * @param int    $entity_guid    Guid of Entity + * @param string $entity_type    Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name           Name of annotation + * @param string $value          Value of annotation + * @param string $value_type     Type of value + * @param int    $owner_guid     GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'min' + * + * @return int + */ +function get_annotations_avg($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { +	elgg_deprecated_notice('get_annotations_avg() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "avg"', 1.8); + +	return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, +			$name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'avg'); +} + + +/** + * Perform a mathmatical calculation on integer annotations. + * + * @param string $sum            What sort of calculation to perform + * @param int    $entity_guid    Guid of Entity + * @param string $entity_type    Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name           Name of annotation + * @param string $value          Value of annotation + * @param string $value_type     Type of value + * @param int    $owner_guid     GUID of owner of annotation + * @param int    $timelower      Lower time limit + * @param int    $timeupper      Upper time limit + * + * @return int + * @deprecated 1.8 Use elgg_get_annotations() and pass anntoation_calculation => <calculation> + */ +function get_annotations_calculate_x($sum = "avg", $entity_guid, $entity_type = "", +$entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0, +$timelower = 0, $timeupper = 0) { +	elgg_deprecated_notice('get_annotations_calculate_x() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "calculation"', 1.8); + +	return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, +			$name, $value, $value_type, $owner_guid, $timelower, $timeupper, $sum); +} + + +/** + * Lists entities by the totals of a particular kind of annotation AND + * the value of a piece of metadata + * + * @param string  $entity_type    Type of entity. + * @param string  $entity_subtype Subtype of entity. + * @param string  $name           Name of annotation. + * @param string  $mdname         Metadata name + * @param string  $mdvalue        Metadata value + * @param int     $limit          Maximum number of results to return. + * @param int     $owner_guid     Owner. + * @param int     $group_guid     Group container. Currently only supported if entity_type is object + * @param boolean $asc            Whether to list in ascending or descending order (default: desc) + * @param boolean $fullview       Whether to display the entities in full + * @param boolean $listtypetoggle Can the 'gallery' view can be displayed (default: no) + * @param boolean $pagination     Display pagination + * @param string  $orderdir       'desc' or 'asc' + * + * @deprecated 1.8 Use elgg_list_entities_from_annotation_calculation(). + * + * @return string Formatted entity list + */ +function list_entities_from_annotation_count_by_metadata($entity_type = "", $entity_subtype = "", +$name = "", $mdname = '', $mdvalue = '', $limit = 10, $owner_guid = 0, $group_guid = 0, +$asc = false, $fullview = true, $listtypetoggle = false, $pagination = true, $orderdir = 'desc') { + +	$msg = 'list_entities_from_annotation_count_by_metadata() is deprecated by elgg_list_entities_from_annotation_calculation().'; + +	elgg_deprecated_notice($msg, 1.8); + +	$options = array(); + +	$options['calculation'] = 'sum'; + +	if ($entity_type) { +		$options['types'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtypes'] = $entity_subtype; +	} + +	$options['annotation_names'] = $name; + +	if ($mdname) { +		$options['metadata_name'] = $mdname; +	} + +	if ($mdvalue) { +		$options['metadata_value'] = $mdvalue; +	} + +	if ($owner_guid) { +		if (is_array($owner_guid)) { +			$options['owner_guids'] = $owner_guid; +		} else { +			$options['owner_guid'] = $owner_guid; +		} +	} + +	$options['full_view'] = $fullview; + +	$options['list_type_toggle'] = $listtypetoggle; + +	$options['pagination'] = $pagination; + +	$options['limit'] = $limit; + +	$options['order_by'] = "annotation_calculation $orderdir"; + +	return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Set an alternative base location for a view (as opposed to the default of $CONFIG->viewpath) + * + * @param string $view The name of the view + * @param string $location The base location path + * + * @deprecated 1.8 Use elgg_set_view_location() + */ +function set_view_location($view, $location, $viewtype = '') { +	elgg_deprecated_notice("set_view_location() was deprecated by elgg_set_view_location()", 1.8); +	return elgg_set_view_location($view, $location, $viewtype); +} + +/** + * Sets the URL handler for a particular entity type and subtype + * + * @param string $function_name The function to register + * @param string $entity_type The entity type + * @param string $entity_subtype The entity subtype + * @return true|false Depending on success + * + * @deprecated 1.8 Use elgg_register_entity_url_handler() + */ +function register_entity_url_handler($function_name, $entity_type = "all", $entity_subtype = "all") { +	elgg_deprecated_notice("register_entity_url_handler() was deprecated by elgg_register_entity_url_handler()", 1.8); +	return elgg_register_entity_url_handler($entity_type, $entity_subtype, $function_name); +} + + +/** + * Get the metadata where the entities they are referring to match a given criteria. + * + * @param mixed  $meta_name      Metadata name + * @param mixed  $meta_value     Metadata value + * @param string $entity_type    The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int    $limit          Limit + * @param int    $offset         Offset + * @param string $order_by       Optional ordering. + * @param int    $site_guid      Site GUID. 0 for current, -1 for any + * + * @return mixed + * @deprecated 1.8 Use elgg_get_metadata() + */ +function find_metadata($meta_name = "", $meta_value = "", $entity_type = "", $entity_subtype = "", +	$limit = 10, $offset = 0, $order_by = "", $site_guid = 0) { + +	elgg_deprecated_notice('get_metadata() is deprecated by elgg_get_metadata()', 1.8); + +	$options = array(); + +	if ($meta_name) { +		$options['annotation_name'] = $meta_name; +	} + +	if ($meta_value) { +		$options['annotation_value'] = $meta_value; +	} + +	if ($entity_type) { +		$options['type'] = $entity_type; +	} + +	if ($entity_subtype) { +		$options['subtype'] = $entity_subtype; +	} + +	$options['limit'] = $limit; +	$options['offset'] = $offset; + +	if ($order_by == 'desc') { +		$options['order_by'] = 'n_table.time_created desc'; +	} + +	if ($site_guid) { +		$options['site_guid'] = $site_guid; +	} + +	return elgg_get_metadata($options); +} + +/** + * Get metadata objects by name. + * + * @param int    $entity_guid Entity GUID + * @param string $meta_name   Metadata name + * + * @return mixed ElggMetadata object, an array of ElggMetadata or false. + * @deprecated 1.8 Use elgg_get_metadata() + */ +function get_metadata_byname($entity_guid, $meta_name) { +	elgg_deprecated_notice('get_metadata_byname() is deprecated by elgg_get_metadata()', 1.8); + +	if (!$entity_guid || !$meta_name) { +		return false; +	} + +	$options = array( +		'guid' => $entity_guid, +		'metadata_name' => $meta_name, +		'limit' => 0 +	); + +	$md = elgg_get_metadata($options); + +	if ($md && count($md) == 1) { +		return $md[0]; +	} + +	return $md; +} + +/** + * Return all the metadata for a given GUID. + * + * @param int $entity_guid Entity GUID + * + * @return mixed + * @deprecated 1.8 Use elgg_get_metadata() + */ +function get_metadata_for_entity($entity_guid) { +	elgg_deprecated_notice('get_metadata_for_entity() is deprecated by elgg_get_metadata()', 1.8); + +	if (!$entity_guid) { +		return false; +	} + +	$options = array( +		'guid' => $entity_guid, +		'limit' => 0 +	); + +	return elgg_get_metadata($options); +} + +/** + * Get a specific metadata object. + * + * @param int $id The id of the metadata being retrieved. + * + * @return mixed False on failure or ElggMetadata + * @deprecated 1.8 Use elgg_get_metadata_from_id() + */ +function get_metadata($id) { +	elgg_deprecated_notice('get_metadata() is deprecated by elgg_get_metadata_from_id()', 1.8); +	return elgg_get_metadata_from_id($id); +} + +/** + * Clear all the metadata for a given entity, assuming you have access to that entity. + * + * @param int $guid Entity GUID + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function clear_metadata($guid) { +	elgg_deprecated_notice('clear_metadata() is deprecated by elgg_delete_metadata()', 1.8); +	if (!$guid) { +		return false; +	} +	return elgg_delete_metadata(array('guid' => $guid, 'limit' => 0)); +} + +/** + * Clear all metadata belonging to a given owner_guid + * + * @param int $owner_guid The owner + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function clear_metadata_by_owner($owner_guid) { +	elgg_deprecated_notice('clear_metadata() is deprecated by elgg_delete_metadata()', 1.8); +	if (!$owner_guid) { +		return false; +	} +	return elgg_delete_metadata(array('metadata_owner_guid' => $owner_guid, 'limit' => 0)); +} + +/** + * Delete a piece of metadata, where the current user has access. + * + * @param int $id The id of metadata to delete. + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function delete_metadata($id) { +	elgg_deprecated_notice('delete_metadata() is deprecated by elgg_delete_metadata()', 1.8); +	if (!$id) { +		return false; +	} +	return elgg_delete_metadata(array('metadata_id' => $id)); +} + +/** + * Removes metadata on an entity with a particular name, optionally with a given value. + * + * @param int    $guid  The entity GUID + * @param string $name  The name of the metadata + * @param string $value The value of the metadata (useful to remove a single item of a set) + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function remove_metadata($guid, $name, $value = "") { +	elgg_deprecated_notice('delete_metadata() is deprecated by elgg_delete_metadata()', 1.8); + +	// prevent them from deleting everything +	if (!$guid) { +		return false; +	} + +	$options = array( +		'guid' => $guid, +		'metadata_name' => $name, +		'limit' => 0 +	); + +	if ($value) { +		$options['metadata_value'] = $value; +	} + +	return elgg_delete_metadata($options); +} + +/** + * Get a specific annotation. + * + * @param int $annotation_id Annotation ID + * + * @return ElggAnnotation + * @deprecated 1.8 Use elgg_get_annotation_from_id() + */ +function get_annotation($annotation_id) { +	elgg_deprecated_notice('get_annotation() is deprecated by elgg_get_annotation_from_id()', 1.8); +	return elgg_get_annotation_from_id($annotation_id); +} + +/** + * Delete a given annotation. + * + * @param int $id The annotation id + * + * @return bool + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function delete_annotation($id) { +	elgg_deprecated_notice('delete_annotation() is deprecated by elgg_delete_annotations()', 1.8); +	if (!$id) { +		return false; +	} +	return elgg_delete_annotations(array('annotation_id' => $annotation_id)); +} + +/** + * Clear all the annotations for a given entity, assuming you have access to that metadata. + * + * @param int    $guid The entity guid + * @param string $name The name of the annotation to delete. + * + * @return int Number of annotations deleted or false if an error + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function clear_annotations($guid, $name = "") { +	elgg_deprecated_notice('clear_annotations() is deprecated by elgg_delete_annotations()', 1.8); + +	if (!$guid) { +		return false; +	} + +	$options = array( +		'guid' => $guid, +		'limit' => 0 +	); + +	if ($name) { +		$options['annotation_name'] = $name; +	} + +	return elgg_delete_annotations($options); +} + +/** + * Clear all annotations belonging to a given owner_guid + * + * @param int $owner_guid The owner + * + * @return int Number of annotations deleted + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function clear_annotations_by_owner($owner_guid) { +	elgg_deprecated_notice('clear_annotations_by_owner() is deprecated by elgg_delete_annotations()', 1.8); + +	if (!$owner_guid) { +		return false; +	} + +	$options = array( +		'annotation_owner_guid' => $guid, +		'limit' => 0 +	); + +	return elgg_delete_annotations($options); +} + +/** + * Registers a page handler for a particular identifier + * + * For example, you can register a function called 'blog_page_handler' for handler type 'blog' + * Now for all URLs of type http://yoururl/pg/blog/*, the blog_page_handler() function will be called. + * The part of the URL marked with * above will be exploded on '/' characters and passed as an + * array to that function. + * For example, the URL http://yoururl/blog/username/friends/ would result in the call: + * blog_page_handler(array('username','friends'), blog); + * + * Page handler functions should return true or the default page handler will be called. + * + * A request to register a page handler with the same identifier as previously registered + * handler will replace the previous one. + * + * The context is set to the page handler identifier before the registered + * page handler function is called. For the above example, the context is set to 'blog'. + * + * @param string $handler The page type to handle + * @param string $function Your function name + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_page_handler()} + */ +function register_page_handler($handler, $function){ +	elgg_deprecated_notice("register_page_handler() was deprecated by elgg_register_page_handler()", 1.8); +	return elgg_register_page_handler($handler, $function); +} + +/** + * Unregister a page handler for an identifier + * + * Note: to replace a page handler, call register_page_handler() + * + * @param string $handler The page type identifier + * @since 1.7.2 + *  + * @deprecated 1.8 Use {@link elgg_unregister_page_handler()} + */ +function unregister_page_handler($handler) { +	elgg_deprecated_notice("unregister_page_handler() was deprecated by elgg_unregister_page_handler()", 1.8); +	return elgg_unregister_page_handler($handler); +} + +/** + * Register an annotation url handler. + * + * @param string $function_name The function. + * @param string $extender_name The name, default 'all'. + * + * @deprecated 1.8 Use {@link elgg_register_annotation_url_handler()} + */ +function register_annotation_url_handler($function, $extender_name) { +	elgg_deprecated_notice("register_annotation_url_handler() was deprecated by elgg_register_annotation_url_handler()", 1.8); +	return elgg_register_annotation_url_handler($extender_name, $function); +} + +/** + * Sets the URL handler for a particular extender type and name. + * It is recommended that you do not call this directly, instead use one of the wrapper functions in the + * subtype files. + * + * @param string $function_name The function to register + * @param string $extender_type Extender type + * @param string $extender_name The name of the extender + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_extender_url_handler()} + */ +function register_extender_url_handler($function, $type = "all", $name = "all") { +	elgg_deprecated_notice("register_extender_url_handler() was deprecated by elgg_register_extender_url_handler()", 1.8); +	return elgg_register_extender_url_handler($type, $name, $function); +} + +/** + * Registers and entity type and subtype to return in search and other places. + * A description in the elgg_echo languages file of the form item:type:subtype + * is also expected. + * + * @param string $type The type of entity (object, site, user, group) + * @param string $subtype The subtype to register (may be blank) + * @return true|false Depending on success + *  + * @deprecated 1.8 Use {@link elgg_register_entity_type()} + */ +function register_entity_type($type, $subtype = null) { +	elgg_deprecated_notice("register_entity_type() was deprecated by elgg_register_entity_type()", 1.8); +	return elgg_register_entity_type($type, $subtype); +} + +/** + * Register a metadata url handler. + * + * @param string $function_name The function. + * @param string $extender_name The name, default 'all'. + *  + * @deprecated 1.8 Use {@link elgg_register_metadata_url_handler()} + */ +function register_metadata_url_handler($function, $extender_name = "all") { +	return elgg_register_metadata_url_handler($extender_name, $function); +} + +/** + * Sets the URL handler for a particular relationship type + * + * @param string $function_name The function to register + * @param string $relationship_type The relationship type. + * @return true|false Depending on success + *  + * @deprecated 1.8 Use {@link elgg_register_relationship_url_handler()} + */ +function register_relationship_url_handler($function_name, $relationship_type = "all") { +	elgg_deprecated_notice("register_relationship_url_handler() was deprecated by elgg_register_relationship_url_handler()", 1.8); +	return elgg_register_relationship_url_handler($relationship_type, $function_name); +} + +/** + * Registers a view to be simply cached + * + * Views cached in this manner must take no parameters and be login agnostic - + * that is to say, they look the same no matter who is logged in (or logged out). + * + * CSS and the basic jS views are automatically cached like this. + * + * @param string $viewname View name + * + * @deprecated 1.8 Use {@link elgg_register_simplecache_view()} + */ +function elgg_view_register_simplecache($viewname) { +	elgg_deprecated_notice("elgg_view_register_simplecache() was deprecated by elgg_register_simplecache_view()", 1.8); +	return elgg_register_simplecache_view($viewname); +} + +/** + * Regenerates the simple cache. + * + * @param string $viewtype Optional viewtype to regenerate + * @see elgg_view_register_simplecache() + * + * @deprecated 1.8 Use {@link elgg_regenerate_simplecache()} + */ +function elgg_view_regenerate_simplecache($viewtype = NULL) { +	elgg_deprecated_notice("elgg_view_regenerate_simplecache() was deprecated by elgg_regenerate_simplecache()", 1.8); +	return elgg_regenerate_simplecache($viewtype); +} + +/** + * Enables the simple cache. + * + * @see elgg_view_register_simplecache() + *  + * @deprecated 1.8 Use {@link elgg_enable_simplecache()} + */ +function elgg_view_enable_simplecache() { +	elgg_deprecated_notice("elgg_view_enable_simplecache() was deprecated by elgg_enable_simplecache()", 1.8); +	return elgg_enable_simplecache(); +} + +/** + * Disables the simple cache. + * + * @see elgg_view_register_simplecache() + *  + * @deprecated 1.8 Use {@link elgg_disable_simplecache()} + */ +function elgg_view_disable_simplecache() { +	elgg_deprecated_notice("elgg_view_disable_simplecache() was deprecated by elgg_disable_simplecache()", 1.8); +	return elgg_disable_simplecache(); +} + +// these were internal functions that perhaps can be removed rather than deprecated +/** + * @deprecated 1.8 + */ +function is_db_installed() { +	elgg_deprecated_notice('is_db_installed() has been deprecated', 1.8); +	return true; +} + +/** + * @deprecated 1.8 + */ +function is_installed() { +	elgg_deprecated_notice('is_installed() has been deprecated', 1.8); +	return true; +} + +/** + * Attempt to authenticate. + * This function will process all registered PAM handlers or stop when the first + * handler fails. A handler fails by either returning false or throwing an + * exception. The advantage of throwing an exception is that it returns a message + * through the global $_PAM_HANDLERS_MSG which can be used in communication with + * a user. The order that handlers are processed is determined by the order that + * they were registered. + * + * If $credentials are provided the PAM handler should authenticate using the + * provided credentials, if not then credentials should be prompted for or + * otherwise retrieved (eg from the HTTP header or $_SESSION). + * + * @param mixed $credentials Mixed PAM handler specific credentials (e.g. username, password) + * @param string $policy - the policy type, default is "user" + * @return bool true if authenticated, false if not. + * + * @deprecated 1.8 See {@link ElggPAM} + */ +function pam_authenticate($credentials = NULL, $policy = "user") { +	elgg_deprecated_notice('pam_authenticate has been deprecated for ElggPAM', 1.8); +	global $_PAM_HANDLERS, $_PAM_HANDLERS_MSG; + +	$_PAM_HANDLERS_MSG = array(); + +	$authenticated = false; + +	foreach ($_PAM_HANDLERS[$policy] as $k => $v) { +		$handler = $v->handler; +		$importance = $v->importance; + +		try { +			// Execute the handler +			if ($handler($credentials)) { +				// Explicitly returned true +				$_PAM_HANDLERS_MSG[$k] = "Authenticated!"; + +				$authenticated = true; +			} else { +				$_PAM_HANDLERS_MSG[$k] = "Not Authenticated."; + +				// If this is required then abort. +				if ($importance == 'required') { +					return false; +				} +			} +		} catch (Exception $e) { +			$_PAM_HANDLERS_MSG[$k] = "$e"; + +			// If this is required then abort. +			if ($importance == 'required') { +				return false; +			} +		} +	} + +	return $authenticated; +} + + +/** + * When given a widget entity and a new requested location, saves the new location + * and also provides a sensible ordering for all widgets in that column + * + * @param ElggObject $widget The widget entity + * @param int        $order  The order within the column + * @param int        $column The column (1, 2 or 3) + * + * @return bool Depending on success + * @deprecated 1.8 use ElggWidget::move() + */ +function save_widget_location(ElggObject $widget, $order, $column) { +	elgg_deprecated_notice('save_widget_location() is deprecated', 1.8); +	if ($widget instanceof ElggObject) { +		if ($widget->subtype == "widget") { +			// If you can't move the widget, don't save a new location +			if (!$widget->draggable) { +				return false; +			} + +			// Sanitise the column value +			if ($column != 1 || $column != 2 || $column != 3) { +				$column = 1; +			} + +			$widget->column = (int) $column; + +			$ordertmp = array(); +			$params = array( +				'context' => $widget->context, +				'column' => $column, +			); + +			if ($entities = get_entities_from_metadata_multi($params, 'object', 'widget')) { +				foreach ($entities as $entity) { +					$entityorder = $entity->order; +					if ($entityorder < $order) { +						$ordertmp[$entityorder] = $entity; +					} +					if ($entityorder >= $order) { +						$ordertmp[$entityorder + 10000] = $entity; +					} +				} +			} + +			$ordertmp[$order] = $widget; +			ksort($ordertmp); + +			$orderticker = 10; +			foreach ($ordertmp as $orderval => $entity) { +				$entity->order = $orderticker; +				$orderticker += 10; +			} + +			return true; +		} else { +			register_error($widget->subtype); +		} + +	} + +	return false; +} + +/** + * Get widgets for a particular context and column, in order of display + * + * @param int    $user_guid The owner user GUID + * @param string $context   The context (profile, dashboard etc) + * @param int    $column    The column (1 or 2) + * + * @return array|false An array of widget ElggObjects, or false + * @deprecated 1.8 Use elgg_get_widgets() + */ +function get_widgets($user_guid, $context, $column) { +	elgg_deprecated_notice('get_widgets is depecated for elgg_get_widgets', 1.8); +	$params = array( +		'column' => $column, +		'context' => $context +	); +	$widgets = get_entities_from_private_setting_multi($params, "object", +		"widget", $user_guid, "", 10000); + +	if ($widgets) { +		$widgetorder = array(); +		foreach ($widgets as $widget) { +			$order = $widget->order; +			while (isset($widgetorder[$order])) { +				$order++; +			} +			$widgetorder[$order] = $widget; +		} + +		ksort($widgetorder); + +		return $widgetorder; +	} + +	return false; +} + +/** + * Add a new widget instance + * + * @param int    $entity_guid GUID of entity that owns this widget + * @param string $handler     The handler for this widget + * @param string $context     The page context for this widget + * @param int    $order       The order to display this widget in + * @param int    $column      The column to display this widget in (1, 2 or 3) + * @param int    $access_id   If not specified, it is set to the default access level + * + * @return int|false Widget GUID or false on failure + * @deprecated 1.8 use elgg_create_widget() + */ +function add_widget($entity_guid, $handler, $context, $order = 0, $column = 1, $access_id = null) { +	elgg_deprecated_notice('add_widget has been deprecated for elgg_create_widget', 1.8); +	if (empty($entity_guid) || empty($context) || empty($handler) || !widget_type_exists($handler)) { +		return false; +	} + +	if ($entity = get_entity($entity_guid)) { +		$widget = new ElggWidget; +		$widget->owner_guid = $entity_guid; +		$widget->container_guid = $entity_guid; +		if (isset($access_id)) { +			$widget->access_id = $access_id; +		} else { +			$widget->access_id = get_default_access(); +		} + +		$guid = $widget->save(); + +		// private settings cannot be set until ElggWidget saved +		$widget->handler = $handler; +		$widget->context = $context; +		$widget->column = $column; +		$widget->order = $order; + +		return $guid; +	} + +	return false; +} + +/** + * Define a new widget type + * + * @param string $handler     The identifier for the widget handler + * @param string $name        The name of the widget type + * @param string $description A description for the widget type + * @param string $context     A comma-separated list of contexts where this + *                            widget is allowed (default: 'all') + * @param bool   $multiple    Whether or not multiple instances of this widget + *                            are allowed on a single dashboard (default: false) + * @param string $positions   A comma-separated list of positions on the page + *                            (side or main) where this widget is allowed (default: "side,main") + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_register_widget_type + */ +function add_widget_type($handler, $name, $description, $context = "all", +$multiple = false, $positions = "side,main") { +	elgg_deprecated_notice("add_widget_type deprecated for elgg_register_widget_type", 1.8); + +	return elgg_register_widget_type($handler, $name, $description, $context, $multiple); +} + +/** + * Remove a widget type + * + * @param string $handler The identifier for the widget handler + * + * @return void + * @since 1.7.1 + * @deprecated 1.8 Use elgg_unregister_widget_type + */ +function remove_widget_type($handler) { +	elgg_deprecated_notice("remove_widget_type deprecated for elgg_unregister_widget_type", 1.8); +	return elgg_unregister_widget_type($handler); +} + +/** + * Determines whether or not widgets with the specified handler have been defined + * + * @param string $handler The widget handler identifying string + * + * @return bool Whether or not those widgets exist + * @deprecated 1.8 Use elgg_is_widget_type + */ +function widget_type_exists($handler) { +	elgg_deprecated_notice("widget_type_exists deprecated for elgg_is_widget_type", 1.8); +	return elgg_is_widget_type($handler); +} + +/** + * Returns an array of stdClass objects representing the defined widget types + * + * @return array A list of types defined (if any) + * @deprecated 1.8 Use elgg_get_widget_types + */ +function get_widget_types() { +	elgg_deprecated_notice("get_widget_types deprecrated for elgg_get_widget_types", 1.8); +	return elgg_get_widget_types(); +} + +/** + * Saves a widget's settings (by passing an array of + * (name => value) pairs to save_{$handler}_widget) + * + * @param int   $widget_guid The GUID of the widget we're saving to + * @param array $params      An array of name => value parameters + * + * @return bool + * @deprecated 1.8 Use elgg_save_widget_settings + */ +function save_widget_info($widget_guid, $params) { +	elgg_deprecated_notice("save_widget_info() is deprecated for elgg_save_widget_settings", 1.8); +	if ($widget = get_entity($widget_guid)) { + +		$subtype = $widget->getSubtype(); + +		if ($subtype != "widget") { +			return false; +		} +		$handler = $widget->handler; +		if (empty($handler) || !widget_type_exists($handler)) { +			return false; +		} + +		if (!$widget->canEdit()) { +			return false; +		} + +		// Save the params to the widget +		if (is_array($params) && sizeof($params) > 0) { +			foreach ($params as $name => $value) { + +				if (!empty($name) && !in_array($name, array( +					'guid', 'owner_guid', 'site_guid' +				))) { +					if (is_array($value)) { +						// @todo Handle arrays securely +						$widget->setMetaData($name, $value, "", true); +					} else { +						$widget->$name = $value; +					} +				} +			} +			$widget->save(); +		} + +		$function = "save_{$handler}_widget"; +		if (is_callable($function)) { +			return $function($params); +		} + +		return true; +	} + +	return false; +} + +/** + * Reorders the widgets from a widget panel + * + * @param string $panelstring1 String of guids of ElggWidget objects separated by :: + * @param string $panelstring2 String of guids of ElggWidget objects separated by :: + * @param string $panelstring3 String of guids of ElggWidget objects separated by :: + * @param string $context      Profile or dashboard + * @param int    $owner        Owner guid + * + * @return void + * @deprecated 1.8 Don't use. + */ +function reorder_widgets_from_panel($panelstring1, $panelstring2, $panelstring3, $context, $owner) { +	elgg_deprecated_notice("reorder_widgets_from_panel() is deprecated", 1.8); +	$return = true; + +	$mainwidgets = explode('::', $panelstring1); +	$sidewidgets = explode('::', $panelstring2); +	$rightwidgets = explode('::', $panelstring3); + +	$handlers = array(); +	$guids = array(); + +	if (is_array($mainwidgets) && sizeof($mainwidgets) > 0) { +		foreach ($mainwidgets as $widget) { + +			$guid = (int) $widget; + +			if ("{$guid}" == "{$widget}") { +				$guids[1][] = $widget; +			} else { +				$handlers[1][] = $widget; +			} +		} +	} +	if (is_array($sidewidgets) && sizeof($sidewidgets) > 0) { +		foreach ($sidewidgets as $widget) { + +			$guid = (int) $widget; + +			if ("{$guid}" == "{$widget}") { +				$guids[2][] = $widget; +			} else { +				$handlers[2][] = $widget; +			} + +		} +	} +	if (is_array($rightwidgets) && sizeof($rightwidgets) > 0) { +		foreach ($rightwidgets as $widget) { + +			$guid = (int) $widget; + +			if ("{$guid}" == "{$widget}") { +				$guids[3][] = $widget; +			} else { +				$handlers[3][] = $widget; +			} + +		} +	} + +	// Reorder existing widgets or delete ones that have vanished +	foreach (array(1, 2, 3) as $column) { +		if ($dbwidgets = get_widgets($owner, $context, $column)) { + +			foreach ($dbwidgets as $dbwidget) { +				if (in_array($dbwidget->getGUID(), $guids[1]) +				|| in_array($dbwidget->getGUID(), $guids[2]) || in_array($dbwidget->getGUID(), $guids[3])) { + +					if (in_array($dbwidget->getGUID(), $guids[1])) { +						$pos = array_search($dbwidget->getGUID(), $guids[1]); +						$col = 1; +					} else if (in_array($dbwidget->getGUID(), $guids[2])) { +						$pos = array_search($dbwidget->getGUID(), $guids[2]); +						$col = 2; +					} else { +						$pos = array_search($dbwidget->getGUID(), $guids[3]); +						$col = 3; +					} +					$pos = ($pos + 1) * 10; +					$dbwidget->column = $col; +					$dbwidget->order = $pos; +				} else { +					$dbguid = $dbwidget->getGUID(); +					if (!$dbwidget->delete()) { +						$return = false; +					} else { +						// Remove state cookie +						setcookie('widget' + $dbguid, null); +					} +				} +			} + +		} +		// Add new ones +		if (sizeof($guids[$column]) > 0) { +			foreach ($guids[$column] as $key => $guid) { +				if ($guid == 0) { +					$pos = ($key + 1) * 10; +					$handler = $handlers[$column][$key]; +					if (!add_widget($owner, $handler, $context, $pos, $column)) { +						$return = false; +					} +				} +			} +		} +	} + +	return $return; +} + +/** + * Register a particular context for use with widgets. + * + * @param string $context The context we wish to enable context for + * + * @return void + * @deprecated 1.8 Don't use. + */ +function use_widgets($context) { +	elgg_deprecated_notice("use_widgets is deprecated", 1.8); +	global $CONFIG; + +	if (!isset($CONFIG->widgets)) { +		$CONFIG->widgets = new stdClass; +	} + +	if (!isset($CONFIG->widgets->contexts)) { +		$CONFIG->widgets->contexts = array(); +	} + +	if (!empty($context)) { +		$CONFIG->widgets->contexts[] = $context; +	} +} + +/** + * Determines whether or not the current context is using widgets + * + * @return bool Depending on widget status + * @deprecated 1.8 Don't use. + */ +function using_widgets() { +	elgg_deprecated_notice("using_widgets is deprecated", 1.8); +	global $CONFIG; + +	$context = elgg_get_context(); +	if (isset($CONFIG->widgets->contexts) && is_array($CONFIG->widgets->contexts)) { +		if (in_array($context, $CONFIG->widgets->contexts)) { +			return true; +		} +	} + +	return false; +} + +/** + * Displays a particular widget + * + * @param ElggObject $widget The widget to display + * @return string The HTML for the widget, including JavaScript wrapper + *  + * @deprecated 1.8 Use elgg_view_entity() + */ +function display_widget(ElggObject $widget) { +	elgg_deprecated_notice("display_widget() was been deprecated. Use elgg_view_entity().", 1.8); +	return elgg_view_entity($widget); +} + +/** + * Count the number of comments attached to an entity + * + * @param ElggEntity $entity + * @return int Number of comments + * @deprecated 1.8 Use ElggEntity->countComments() + */ +function elgg_count_comments($entity) { +	elgg_deprecated_notice('elgg_count_comments() is deprecated by ElggEntity->countComments()', 1.8); + +	if ($entity instanceof ElggEntity) { +		return $entity->countComments(); +	} + +	return 0; +} + +/** + * Removes all items relating to a particular acting entity from the river + * + * @param int $subject_guid The GUID of the entity + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_subject($subject_guid) { +	elgg_deprecated_notice("remove_from_river_by_subject() deprecated by elgg_delete_river()", 1.8); + +	return elgg_delete_river(array('subject_guid' => $subject_guid)); +} + +/** + * Removes all items relating to a particular entity being acted upon from the river + * + * @param int $object_guid The GUID of the entity + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_object($object_guid) { +	elgg_deprecated_notice("remove_from_river_by_object() deprecated by elgg_delete_river()", 1.8); + +	return elgg_delete_river(array('object_guid' => $object_guid)); +} + +/** + * Removes all items relating to a particular annotation being acted upon from the river + * + * @param int $annotation_id The ID of the annotation + * + * @return bool Depending on success + * @since 1.7.0 + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_annotation($annotation_id) { +	elgg_deprecated_notice("remove_from_river_by_annotation() deprecated by elgg_delete_river()", 1.8); + +	return elgg_delete_river(array('annotation_id' => $annotation_id)); +} + +/** + * Removes a single river entry + * + * @param int $id The ID of the river entry + * + * @return bool Depending on success + * @since 1.7.2 + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_id($id) { +	elgg_deprecated_notice("remove_from_river_by_id() deprecated by elgg_delete_river()", 1.8); + +	return elgg_delete_river(array('id' => $id)); +} + +/** + * A default page handler + * Tries to locate a suitable file to include. Only works for core pages, not plugins. + * + * @param array  $page    The page URL elements + * @param string $handler The base handler + * + * @return true|false Depending on success + * @deprecated 1.8 + */ +function default_page_handler($page, $handler) { +	global $CONFIG; + +	elgg_deprecated_notice("default_page_handler is deprecated", "1.8"); + +	$page = implode('/', $page); + +	// protect against including arbitary files +	$page = str_replace("..", "", $page); + +	$callpath = $CONFIG->path . $handler . "/" . $page; +	if (is_dir($callpath)) { +		$callpath = sanitise_filepath($callpath); +		$callpath .= "index.php"; +		if (file_exists($callpath)) { +			if (include($callpath)) { +				return TRUE; +			} +		} +	} else if (file_exists($callpath)) { +		include($callpath); +		return TRUE; +	} + +	return FALSE; +} + +/** + * Invalidate this class's entry in the cache. + * + * @param int $guid The entity guid + * + * @return void + * @access private + * @deprecated 1.8 + */ +function invalidate_cache_for_entity($guid) { +	elgg_deprecated_notice('invalidate_cache_for_entity() is a private function and should not be used.', 1.8); +	_elgg_invalidate_cache_for_entity($guid); +} + +/** + * Cache an entity. + * + * Stores an entity in $ENTITY_CACHE; + * + * @param ElggEntity $entity Entity to cache + * + * @return void + * @access private + * @deprecated 1.8 + */ +function cache_entity(ElggEntity $entity) { +	elgg_deprecated_notice('cache_entity() is a private function and should not be used.', 1.8); +	_elgg_cache_entity($entity); +} + +/** + * Retrieve a entity from the cache. + * + * @param int $guid The guid + * + * @return ElggEntity|bool false if entity not cached, or not fully loaded + * @access private + * @deprecated 1.8 + */ +function retrieve_cached_entity($guid) { +	elgg_deprecated_notice('retrieve_cached_entity() is a private function and should not be used.', 1.8); +	return _elgg_retrieve_cached_entity($guid); +} diff --git a/engine/lib/deprecated-1.9.php b/engine/lib/deprecated-1.9.php new file mode 100644 index 000000000..31d03428f --- /dev/null +++ b/engine/lib/deprecated-1.9.php @@ -0,0 +1,582 @@ +<?php +/** + * Return a timestamp for the start of a given day (defaults today). + * + * @param int $day   Day + * @param int $month Month + * @param int $year  Year + * + * @return int + * @access private + * @deprecated 1.9 + */ +function get_day_start($day = null, $month = null, $year = null) { +	elgg_deprecated_notice('get_day_start() has been deprecated', 1.9); +	return mktime(0, 0, 0, $month, $day, $year); +} + +/** + * Return a timestamp for the end of a given day (defaults today). + * + * @param int $day   Day + * @param int $month Month + * @param int $year  Year + * + * @return int + * @access private + * @deprecated 1.9 + */ +function get_day_end($day = null, $month = null, $year = null) { +	elgg_deprecated_notice('get_day_end() has been deprecated', 1.9); +	return mktime(23, 59, 59, $month, $day, $year); +} + +/** + * Return the notable entities for a given time period. + * + * @param int     $start_time     The start time as a unix timestamp. + * @param int     $end_time       The end time as a unix timestamp. + * @param string  $type           The type of entity (eg "user", "object" etc) + * @param string  $subtype        The arbitrary subtype of the entity + * @param int     $owner_guid     The GUID of the owning user + * @param string  $order_by       The field to order by; by default, time_created desc + * @param int     $limit          The number of entities to return; 10 by default + * @param int     $offset         The indexing offset, 0 by default + * @param boolean $count          Set to true to get a count instead of entities. Defaults to false. + * @param int     $site_guid      Site to get entities for. Default 0 = current site. -1 = any. + * @param mixed   $container_guid Container or containers to get entities from (default: any). + * + * @return array|false + * @access private + * @deprecated 1.9 + */ +function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "asc", $limit = 10, $offset = 0, $count = false, $site_guid = 0, +$container_guid = null) { +	elgg_deprecated_notice('get_notable_entities() has been deprecated', 1.9); +	global $CONFIG; + +	if ($subtype === false || $subtype === null || $subtype === 0) { +		return false; +	} + +	$start_time = (int)$start_time; +	$end_time = (int)$end_time; +	$order_by = sanitise_string($order_by); +	$limit = (int)$limit; +	$offset = (int)$offset; +	$site_guid = (int) $site_guid; +	if ($site_guid == 0) { +		$site_guid = $CONFIG->site_guid; +	} + +	$where = array(); + +	if (is_array($type)) { +		$tempwhere = ""; +		if (sizeof($type)) { +			foreach ($type as $typekey => $subtypearray) { +				foreach ($subtypearray as $subtypeval) { +					$typekey = sanitise_string($typekey); +					if (!empty($subtypeval)) { +						$subtypeval = (int) get_subtype_id($typekey, $subtypeval); +					} else { +						$subtypeval = 0; +					} +					if (!empty($tempwhere)) { +						$tempwhere .= " or "; +					} +					$tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; +				} +			} +		} +		if (!empty($tempwhere)) { +			$where[] = "({$tempwhere})"; +		} +	} else { +		$type = sanitise_string($type); +		$subtype = get_subtype_id($type, $subtype); + +		if ($type != "") { +			$where[] = "e.type='$type'"; +		} + +		if ($subtype !== "") { +			$where[] = "e.subtype=$subtype"; +		} +	} + +	if ($owner_guid != "") { +		if (!is_array($owner_guid)) { +			$owner_array = array($owner_guid); +			$owner_guid = (int) $owner_guid; +			$where[] = "e.owner_guid = '$owner_guid'"; +		} else if (sizeof($owner_guid) > 0) { +			$owner_array = array_map('sanitise_int', $owner_guid); +			// Cast every element to the owner_guid array to int +			$owner_guid = implode(",", $owner_guid); +			$where[] = "e.owner_guid in ({$owner_guid})"; +		} +		if (is_null($container_guid)) { +			$container_guid = $owner_array; +		} +	} + +	if ($site_guid > 0) { +		$where[] = "e.site_guid = {$site_guid}"; +	} + +	if (!is_null($container_guid)) { +		if (is_array($container_guid)) { +			foreach ($container_guid as $key => $val) { +				$container_guid[$key] = (int) $val; +			} +			$where[] = "e.container_guid in (" . implode(",", $container_guid) . ")"; +		} else { +			$container_guid = (int) $container_guid; +			$where[] = "e.container_guid = {$container_guid}"; +		} +	} + +	// Add the calendar stuff +	$cal_join = " +		JOIN {$CONFIG->dbprefix}metadata cal_start on e.guid=cal_start.entity_guid +		JOIN {$CONFIG->dbprefix}metastrings cal_start_name on cal_start.name_id=cal_start_name.id +		JOIN {$CONFIG->dbprefix}metastrings cal_start_value on cal_start.value_id=cal_start_value.id + +		JOIN {$CONFIG->dbprefix}metadata cal_end on e.guid=cal_end.entity_guid +		JOIN {$CONFIG->dbprefix}metastrings cal_end_name on cal_end.name_id=cal_end_name.id +		JOIN {$CONFIG->dbprefix}metastrings cal_end_value on cal_end.value_id=cal_end_value.id +	"; +	$where[] = "cal_start_name.string='calendar_start'"; +	$where[] = "cal_start_value.string>=$start_time"; +	$where[] = "cal_end_name.string='calendar_end'"; +	$where[] = "cal_end_value.string <= $end_time"; + + +	if (!$count) { +		$query = "SELECT e.* from {$CONFIG->dbprefix}entities e $cal_join where "; +	} else { +		$query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e $cal_join where "; +	} +	foreach ($where as $w) { +		$query .= " $w and "; +	} + +	$query .= get_access_sql_suffix('e'); // Add access controls + +	if (!$count) { +		$query .= " order by n.calendar_start $order_by"; +		// Add order and limit +		if ($limit) { +			$query .= " limit $offset, $limit"; +		} +		$dt = get_data($query, "entity_row_to_elggstar"); + +		return $dt; +	} else { +		$total = get_data_row($query); +		return $total->total; +	} +} + +/** + * Return the notable entities for a given time period based on an item of metadata. + * + * @param int    $start_time     The start time as a unix timestamp. + * @param int    $end_time       The end time as a unix timestamp. + * @param mixed  $meta_name      Metadata name + * @param mixed  $meta_value     Metadata value + * @param string $entity_type    The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int    $owner_guid     Owner GUID + * @param int    $limit          Limit + * @param int    $offset         Offset + * @param string $order_by       Optional ordering. + * @param int    $site_guid      Site to get entities for. Default 0 = current site. -1 = any. + * @param bool   $count          If true, returns count instead of entities. (Default: false) + * + * @return int|array A list of entities, or a count if $count is set to true + * @access private + * @deprecated 1.9 + */ +function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $meta_value = "", +$entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", +$site_guid = 0, $count = false) { +	elgg_deprecated_notice('get_notable_entities_from_metadata() has been deprecated', 1.9); + +	global $CONFIG; + +	$meta_n = get_metastring_id($meta_name); +	$meta_v = get_metastring_id($meta_value); + +	$start_time = (int)$start_time; +	$end_time = (int)$end_time; +	$entity_type = sanitise_string($entity_type); +	$entity_subtype = get_subtype_id($entity_type, $entity_subtype); +	$limit = (int)$limit; +	$offset = (int)$offset; +	if ($order_by == "") { +		$order_by = "e.time_created desc"; +	} +	$order_by = sanitise_string($order_by); +	$site_guid = (int) $site_guid; +	if ((is_array($owner_guid) && (count($owner_guid)))) { +		foreach ($owner_guid as $key => $guid) { +			$owner_guid[$key] = (int) $guid; +		} +	} else { +		$owner_guid = (int) $owner_guid; +	} + +	if ($site_guid == 0) { +		$site_guid = $CONFIG->site_guid; +	} + +	//$access = get_access_list(); + +	$where = array(); + +	if ($entity_type != "") { +		$where[] = "e.type='$entity_type'"; +	} + +	if ($entity_subtype) { +		$where[] = "e.subtype=$entity_subtype"; +	} + +	if ($meta_name != "") { +		$where[] = "m.name_id='$meta_n'"; +	} + +	if ($meta_value != "") { +		$where[] = "m.value_id='$meta_v'"; +	} + +	if ($site_guid > 0) { +		$where[] = "e.site_guid = {$site_guid}"; +	} + +	if (is_array($owner_guid)) { +		$where[] = "e.container_guid in (" . implode(",", $owner_guid) . ")"; +	} else if ($owner_guid > 0) { +		$where[] = "e.container_guid = {$owner_guid}"; +	} + +	// Add the calendar stuff +	$cal_join = " +		JOIN {$CONFIG->dbprefix}metadata cal_start on e.guid=cal_start.entity_guid +		JOIN {$CONFIG->dbprefix}metastrings cal_start_name on cal_start.name_id=cal_start_name.id +		JOIN {$CONFIG->dbprefix}metastrings cal_start_value on cal_start.value_id=cal_start_value.id + +		JOIN {$CONFIG->dbprefix}metadata cal_end on e.guid=cal_end.entity_guid +		JOIN {$CONFIG->dbprefix}metastrings cal_end_name on cal_end.name_id=cal_end_name.id +		JOIN {$CONFIG->dbprefix}metastrings cal_end_value on cal_end.value_id=cal_end_value.id +	"; + +	$where[] = "cal_start_name.string='calendar_start'"; +	$where[] = "cal_start_value.string>=$start_time"; +	$where[] = "cal_end_name.string='calendar_end'"; +	$where[] = "cal_end_value.string <= $end_time"; + +	if (!$count) { +		$query = "SELECT distinct e.* "; +	} else { +		$query = "SELECT count(distinct e.guid) as total "; +	} + +	$query .= "from {$CONFIG->dbprefix}entities e" +	. " JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid $cal_join where"; + +	foreach ($where as $w) { +		$query .= " $w and "; +	} + +	// Add access controls +	$query .= get_access_sql_suffix("e"); +	$query .= ' and ' . get_access_sql_suffix("m"); + +	if (!$count) { +		// Add order and limit +		$query .= " order by $order_by limit $offset, $limit"; +		return get_data($query, "entity_row_to_elggstar"); +	} else { +		if ($row = get_data_row($query)) { +			return $row->total; +		} +	} + +	return false; +} + +/** + * Return the notable entities for a given time period based on their relationship. + * + * @param int     $start_time           The start time as a unix timestamp. + * @param int     $end_time             The end time as a unix timestamp. + * @param string  $relationship         The relationship eg "friends_of" + * @param int     $relationship_guid    The guid of the entity to use query + * @param bool    $inverse_relationship Reverse the normal function of the query to say + *                                      "give me all entities for whom $relationship_guid is a + *                                      $relationship of" + * @param string  $type                 Entity type + * @param string  $subtype              Entity subtype + * @param int     $owner_guid           Owner GUID + * @param string  $order_by             Optional Order by + * @param int     $limit                Limit + * @param int     $offset               Offset + * @param boolean $count                If true returns a count of entities (default false) + * @param int     $site_guid            Site to get entities for. Default 0 = current site. -1 = any + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private + * @deprecated 1.9 + */ +function get_noteable_entities_from_relationship($start_time, $end_time, $relationship, +$relationship_guid, $inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { +	elgg_deprecated_notice('get_noteable_entities_from_relationship() has been deprecated', 1.9); + +	global $CONFIG; + +	$start_time = (int)$start_time; +	$end_time = (int)$end_time; +	$relationship = sanitise_string($relationship); +	$relationship_guid = (int)$relationship_guid; +	$inverse_relationship = (bool)$inverse_relationship; +	$type = sanitise_string($type); +	$subtype = get_subtype_id($type, $subtype); +	$owner_guid = (int)$owner_guid; +	if ($order_by == "") { +		$order_by = "time_created desc"; +	} +	$order_by = sanitise_string($order_by); +	$limit = (int)$limit; +	$offset = (int)$offset; +	$site_guid = (int) $site_guid; +	if ($site_guid == 0) { +		$site_guid = $CONFIG->site_guid; +	} + +	//$access = get_access_list(); + +	$where = array(); + +	if ($relationship != "") { +		$where[] = "r.relationship='$relationship'"; +	} +	if ($relationship_guid) { +		$where[] = $inverse_relationship ? +			"r.guid_two='$relationship_guid'" : "r.guid_one='$relationship_guid'"; +	} +	if ($type != "") { +		$where[] = "e.type='$type'"; +	} +	if ($subtype) { +		$where[] = "e.subtype=$subtype"; +	} +	if ($owner_guid != "") { +		$where[] = "e.container_guid='$owner_guid'"; +	} +	if ($site_guid > 0) { +		$where[] = "e.site_guid = {$site_guid}"; +	} + +	// Add the calendar stuff +	$cal_join = " +		JOIN {$CONFIG->dbprefix}metadata cal_start on e.guid=cal_start.entity_guid +		JOIN {$CONFIG->dbprefix}metastrings cal_start_name on cal_start.name_id=cal_start_name.id +		JOIN {$CONFIG->dbprefix}metastrings cal_start_value on cal_start.value_id=cal_start_value.id + +		JOIN {$CONFIG->dbprefix}metadata cal_end on e.guid=cal_end.entity_guid +		JOIN {$CONFIG->dbprefix}metastrings cal_end_name on cal_end.name_id=cal_end_name.id +		JOIN {$CONFIG->dbprefix}metastrings cal_end_value on cal_end.value_id=cal_end_value.id +	"; +	$where[] = "cal_start_name.string='calendar_start'"; +	$where[] = "cal_start_value.string>=$start_time"; +	$where[] = "cal_end_name.string='calendar_end'"; +	$where[] = "cal_end_value.string <= $end_time"; + +	// Select what we're joining based on the options +	$joinon = "e.guid = r.guid_one"; +	if (!$inverse_relationship) { +		$joinon = "e.guid = r.guid_two"; +	} + +	if ($count) { +		$query = "SELECT count(distinct e.guid) as total "; +	} else { +		$query = "SELECT distinct e.* "; +	} +	$query .= " from {$CONFIG->dbprefix}entity_relationships r" +	. " JOIN {$CONFIG->dbprefix}entities e on $joinon $cal_join where "; + +	foreach ($where as $w) { +		$query .= " $w and "; +	} +	// Add access controls +	$query .= get_access_sql_suffix("e"); +	if (!$count) { +		$query .= " order by $order_by limit $offset, $limit"; // Add order and limit +		return get_data($query, "entity_row_to_elggstar"); +	} else { +		if ($count = get_data_row($query)) { +			return $count->total; +		} +	} +	return false; +} + +/** + * Get all entities for today. + * + * @param string  $type           The type of entity (eg "user", "object" etc) + * @param string  $subtype        The arbitrary subtype of the entity + * @param int     $owner_guid     The GUID of the owning user + * @param string  $order_by       The field to order by; by default, time_created desc + * @param int     $limit          The number of entities to return; 10 by default + * @param int     $offset         The indexing offset, 0 by default + * @param boolean $count          If true returns a count of entities (default false) + * @param int     $site_guid      Site to get entities for. Default 0 = current site. -1 = any + * @param mixed   $container_guid Container(s) to get entities from (default: any). + * + * @return array|false + * @access private + * @deprecated 1.9 + */ +function get_todays_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", +$limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = null) { +	elgg_deprecated_notice('get_todays_entities() has been deprecated', 1.9); + +	$day_start = get_day_start(); +	$day_end = get_day_end(); + +	return get_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $order_by, +		$limit, $offset, $count, $site_guid, $container_guid); +} + +/** + * Get entities for today from metadata. + * + * @param mixed  $meta_name      Metadata name + * @param mixed  $meta_value     Metadata value + * @param string $entity_type    The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int    $owner_guid     Owner GUID + * @param int    $limit          Limit + * @param int    $offset         Offset + * @param string $order_by       Optional ordering. + * @param int    $site_guid      Site to get entities for. Default 0 = current site. -1 = any. + * @param bool   $count          If true, returns count instead of entities. (Default: false) + * + * @return int|array A list of entities, or a count if $count is set to true + * @access private + * @deprecated 1.9 + */ +function get_todays_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", +$entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, +$count = false) { +	elgg_deprecated_notice('get_todays_entities_from_metadata() has been deprecated', 1.9); + +	$day_start = get_day_start(); +	$day_end = get_day_end(); + +	return get_notable_entities_from_metadata($day_start, $day_end, $meta_name, $meta_value, +		$entity_type, $entity_subtype, $owner_guid, $limit, $offset, $order_by, $site_guid, $count); +} + +/** + * Get entities for today from a relationship + * + * @param string  $relationship         The relationship eg "friends_of" + * @param int     $relationship_guid    The guid of the entity to use query + * @param bool    $inverse_relationship Reverse the normal function of the query to say + *                                      "give me all entities for whom $relationship_guid is a + *                                      $relationship of" + * @param string  $type                 Entity type + * @param string  $subtype              Entity subtype + * @param int     $owner_guid           Owner GUID + * @param string  $order_by             Optional Order by + * @param int     $limit                Limit + * @param int     $offset               Offset + * @param boolean $count                If true returns a count of entities (default false) + * @param int     $site_guid            Site to get entities for. Default 0 = current site. -1 = any + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private + * @deprecated 1.9 + */ +function get_todays_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { +	elgg_deprecated_notice('get_todays_entities_from_relationship() has been deprecated', 1.9); + +	$day_start = get_day_start(); +	$day_end = get_day_end(); + +	return get_notable_entities_from_relationship($day_start, $day_end, $relationship, +		$relationship_guid,	$inverse_relationship, $type, $subtype, $owner_guid, $order_by, +		$limit, $offset, $count, $site_guid); +} + +/** + * Returns a viewable list of entities for a given time period. + * + * @see elgg_view_entity_list + * + * @param int     $start_time     The start time as a unix timestamp. + * @param int     $end_time       The end time as a unix timestamp. + * @param string  $type           The type of entity (eg "user", "object" etc) + * @param string  $subtype        The arbitrary subtype of the entity + * @param int     $owner_guid     The GUID of the owning user + * @param int     $limit          The number of entities to return; 10 by default + * @param boolean $fullview       Whether or not to display the full view (default: true) + * @param boolean $listtypetoggle Whether or not to allow gallery view + * @param boolean $navigation     Display pagination? Default: true + * + * @return string A viewable list of entities + * @access private + * @deprecated 1.9 + */ +function list_notable_entities($start_time, $end_time, $type= "", $subtype = "", $owner_guid = 0, +$limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { +	elgg_deprecated_notice('list_notable_entities() has been deprecated', 1.9); + +	$offset = (int) get_input('offset'); +	$count = get_notable_entities($start_time, $end_time, $type, $subtype, +		$owner_guid, "", $limit, $offset, true); + +	$entities = get_notable_entities($start_time, $end_time, $type, $subtype, +		$owner_guid, "", $limit, $offset); + +	return elgg_view_entity_list($entities, $count, $offset, $limit, +		$fullview, $listtypetoggle, $navigation); +} + +/** + * Return a list of today's entities. + * + * @see list_notable_entities + * + * @param string  $type           The type of entity (eg "user", "object" etc) + * @param string  $subtype        The arbitrary subtype of the entity + * @param int     $owner_guid     The GUID of the owning user + * @param int     $limit          The number of entities to return; 10 by default + * @param boolean $fullview       Whether or not to display the full view (default: true) + * @param boolean $listtypetoggle Whether or not to allow gallery view + * @param boolean $navigation     Display pagination? Default: true + * + * @return string A viewable list of entities + * @access private + * @deprecated 1.9 + */ +function list_todays_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, +$fullview = true, $listtypetoggle = false, $navigation = true) { +	elgg_deprecated_notice('list_todays_entities() has been deprecated', 1.9); + +	$day_start = get_day_start(); +	$day_end = get_day_end(); + +	return list_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $limit, +		$fullview, $listtypetoggle, $navigation); +} diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php new file mode 100644 index 000000000..34111c69d --- /dev/null +++ b/engine/lib/elgglib.php @@ -0,0 +1,2304 @@ +<?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 + * @todo return boolean in 1.9 to indicate whether the library has been loaded + */ +function elgg_load_library($name) { +	global $CONFIG; + +	static $loaded_libraries = array(); + +	if (in_array($name, $loaded_libraries)) { +		return; +	} + +	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); +	} + +	$loaded_libraries[] = $name; +} + +/** + * 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. + * @throws SecurityException + */ +function forward($location = "", $reason = 'system') { +	if (!headers_sent($file, $line)) { +		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', array($file, $line))); +	} +} + +/** + * 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 = ''; + +		$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 bool|array Either the array of messages, or a response regarding + *                          whether the message addition was successful. + * @todo Clean up. Separate registering messages and retrieving them. + */ +function system_messages($message = null, $register = "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 $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 on Github. + * + * @internal @tip Think of $object_type as the primary namespace element, and + * $event as the secondary namespace. + * + * @param string $event       The event type + * @param string $object_type The object type + * @param string $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 callable $callback The name of a valid function or an array with object and method + * @param int      $priority The priority - 500 is default, lower numbers called first + * + * @return bool + * + * @example hooks/register/basic.php Registering for a plugin hook and examining the variables. + * @example hooks/register/advanced.php Registering for a plugin hook and changing the params. + * @link http://docs.elgg.org/Tutorials/Plugins/Hooks + * @since 1.8.0 + */ +function 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 callable $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 + * @throws Exception + * @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; +	} + +	// Do not want to write to JS or CSS pages +	if (elgg_in_context('js') || elgg_in_context('css')) { +		$to_screen = FALSE; +	} + +	if ($to_screen == TRUE) { +		echo '<pre>'; +		print_r($value); +		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() { +	$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. +	// https://github.com/Elgg/Elgg/commit/0c947e80f512cb0a482b1864fd0a6965c8a0cd4a +	$quotes = array('\'', '"'); +	$encoded = array('%27', '%22'); + +	return $protocol . "://" . $_SERVER['SERVER_NAME'] . $port . +		str_replace($quotes, $encoded, $_SERVER['REQUEST_URI']); +} + +/** + * 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 string 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 string $url         Full action URL + * @param bool   $html_encode HTML encode the url? (default: false) + * + * @return string URL with action tokens + * @since 1.7.0 + * @link http://docs.elgg.org/Tutorials/Actions + */ +function elgg_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, false); +	return $string; +} + +/** + * Adds an element or elements to a URL's query string. + * + * @param string $url      The URL + * @param array  $elements Key/value pairs to add to the URL + * + * @return string The new URL with the query strings added + * @since 1.7.0 + */ +function elgg_http_add_url_query_elements($url, array $elements) { +	$url_array = parse_url($url); + +	if (isset($url_array['query'])) { +		$query = elgg_parse_str($url_array['query']); +	} else { +		$query = array(); +	} + +	foreach ($elements as $k => $v) { +		$query[$k] = $v; +	} + +	$url_array['query'] = http_build_query($query); +	$string = elgg_http_build_url($url_array, false); + +	return $string; +} + +/** + * 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')) { +	// 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 $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 bool 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 string $setting The php.ini setting + * + * @return int + * @since 1.7.0 + * @link http://www.php.net/manual/en/function.ini-get.php + */ +function elgg_get_ini_setting_in_bytes($setting) { +	// retrieve INI setting +	$val = ini_get($setting); + +	// convert INI setting when shorthand notation is used +	$last = strtolower($val[strlen($val) - 1]); +	switch($last) { +		case 'g': +			$val *= 1024; +			// fallthrough intentional +		case 'm': +			$val *= 1024; +			// fallthrough intentional +		case 'k': +			$val *= 1024; +	} + +	// return byte value +	return $val; +} + +/** + * Returns true is string is not empty, false, or null. + * + * Function to be used in array_filter which returns true if $string is not null. + * + * @param string $string 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 bool + * @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; +	} +	return false; +} + +/** + * 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 array + * @access private + */ +function elgg_api_test($hook, $type, $value, $params) { +	global $CONFIG; +	$value[] = $CONFIG->path . 'engine/tests/api/entity_getter_functions.php'; +	$value[] = $CONFIG->path . 'engine/tests/api/helpers.php'; +	$value[] = $CONFIG->path . 'engine/tests/regression/trac_bugs.php'; +	return $value; +} + +/**#@+ + * Controls access levels on ElggEntity entities, metadata, and annotations. + * + * @warning ACCESS_DEFAULT is a place holder for the input/access view. Do not + * use it when saving an entity. + * + * @var int + */ +define('ACCESS_DEFAULT', -1); +define('ACCESS_PRIVATE', 0); +define('ACCESS_LOGGED_IN', 1); +define('ACCESS_PUBLIC', 2); +define('ACCESS_FRIENDS', -2); +/**#@-*/ + +/** + * Constant to request the value of a parameter be ignored in elgg_get_*() functions + * + * @see elgg_get_entities() + * @var NULL + * @since 1.7 + */ +define('ELGG_ENTITIES_ANY_VALUE', NULL); + +/** + * Constant to request the value of a parameter be nothing in elgg_get_*() functions. + * + * @see elgg_get_entities() + * @var int 0 + * @since 1.7 + */ +define('ELGG_ENTITIES_NO_VALUE', 0); + +/** + * Used in calls to forward() to specify the browser should be redirected to the + * referring page. + * + * @see forward + * @var int -1 + */ +define('REFERRER', -1); + +/** + * Alternate spelling for REFERRER.  Included because of some bad documentation + * in the original HTTP spec. + * + * @see forward() + * @link http://en.wikipedia.org/wiki/HTTP_referrer#Origin_of_the_term_referer + * @var int -1 + */ +define('REFERER', -1); + +elgg_register_event_handler('init', 'system', 'elgg_init'); +elgg_register_event_handler('boot', 'system', '_elgg_engine_boot', 1); +elgg_register_plugin_hook_handler('unit_test', 'system', 'elgg_api_test'); + +elgg_register_event_handler('init', 'system', 'add_custom_menu_items', 1000); +elgg_register_event_handler('init', 'system', 'elgg_walled_garden', 1000); diff --git a/engine/lib/entities.php b/engine/lib/entities.php new file mode 100644 index 000000000..4fcf1c657 --- /dev/null +++ b/engine/lib/entities.php @@ -0,0 +1,2590 @@ +<?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(); + +/** + * GUIDs of entities banned from the entity cache (during this request) + * + * @global array $ENTITY_CACHE_DISABLED_GUIDS + * @access private + */ +global $ENTITY_CACHE_DISABLED_GUIDS; +$ENTITY_CACHE_DISABLED_GUIDS = array(); + +/** + * 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; + +/** + * Remove this entity from the entity cache and make sure it is not re-added + * + * @param int $guid The entity guid + * + * @access private + * @todo this is a workaround until #5604 can be implemented + */ +function _elgg_disable_caching_for_entity($guid) { +	global $ENTITY_CACHE_DISABLED_GUIDS; + +	_elgg_invalidate_cache_for_entity($guid); +	$ENTITY_CACHE_DISABLED_GUIDS[$guid] = true; +} + +/** + * Allow this entity to be stored in the entity cache + * + * @param int $guid The entity guid + * + * @access private + */ +function _elgg_enable_caching_for_entity($guid) { +	global $ENTITY_CACHE_DISABLED_GUIDS; + +	unset($ENTITY_CACHE_DISABLED_GUIDS[$guid]); +} + +/** + * Invalidate this class's entry in the cache. + * + * @param int $guid The entity guid + * + * @return void + * @access private + */ +function _elgg_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 void + * @see _elgg_retrieve_cached_entity() + * @see _elgg_invalidate_cache_for_entity() + * @access private + * @todo Use an ElggCache object + */ +function _elgg_cache_entity(ElggEntity $entity) { +	global $ENTITY_CACHE, $ENTITY_CACHE_DISABLED_GUIDS; + +	// Don't cache non-plugin entities while access control is off, otherwise they could be +	// exposed to users who shouldn't see them when control is re-enabled. +	if (!($entity instanceof ElggPlugin) && elgg_get_ignore_access()) { +		return; +	} + +	$guid = $entity->getGUID(); +	if (isset($ENTITY_CACHE_DISABLED_GUIDS[$guid])) { +		return; +	} + +	// Don't store too many or we'll have memory problems +	// @todo Pick a less arbitrary limit +	if (count($ENTITY_CACHE) > 256) { +		$random_guid = array_rand($ENTITY_CACHE); + +		unset($ENTITY_CACHE[$random_guid]); + +		// Purge separate metadata cache. Original idea was to do in entity destructor, but that would +		// have caused a bunch of unnecessary purges at every shutdown. Doing it this way we have no way +		// to know that the expunged entity will be GCed (might be another reference living), but that's +		// OK; the metadata will reload if necessary. +		elgg_get_metadata_cache()->clear($random_guid); +	} + +	$ENTITY_CACHE[$guid] = $entity; +} + +/** + * Retrieve a entity from the cache. + * + * @param int $guid The guid + * + * @return ElggEntity|bool false if entity not cached, or not fully loaded + * @see _elgg_cache_entity() + * @see _elgg_invalidate_cache_for_entity() + * @access private + */ +function _elgg_retrieve_cached_entity($guid) { +	global $ENTITY_CACHE; + +	if (isset($ENTITY_CACHE[$guid])) { +		if ($ENTITY_CACHE[$guid]->isFullyLoaded()) { +			return $ENTITY_CACHE[$guid]; +		} +	} + +	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 + * @throws InvalidParameterException + * @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 ($access_id == ACCESS_DEFAULT) { +		throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); +	} + +	if ($entity && $entity->canEdit()) { +		if (elgg_trigger_event('update', $entity->type, $entity)) { +			$ret = update_data("UPDATE {$CONFIG->dbprefix}entities +				set owner_guid='$owner_guid', access_id='$access_id', +				container_guid='$container_guid', time_created='$time_created', +				time_updated='$time' WHERE guid=$guid"); + +			if ($entity instanceof ElggObject) { +				update_river_access_by_object($guid, $access_id); +			} + +			// 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) { +			/* @var ElggGroup $container */ +			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; +	$time = time(); +	if ($site_guid == 0) { +		$site_guid = $CONFIG->site_guid; +	} +	$site_guid = (int) $site_guid; +	if ($container_guid == 0) { +		$container_guid = $owner_guid; +	} +	$access_id = (int)$access_id; +	if ($access_id == ACCESS_DEFAULT) { +		throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); +	} + +	$user_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 = _elgg_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 https://github.com/elgg/elgg/issues/3018#issuecomment-13662617 +		if ($cached_entity) { +			// @todo use ACL and cached entity access_id to determine if user can see it +			return $cached_entity; +		} +	} + +	// don't let incomplete entities cause fatal exceptions +	try { +		$new_entity = entity_row_to_elggstar($entity_row); +	} catch (IncompleteEntityException $e) { +		return false; +	} + +	if ($new_entity) { +		_elgg_cache_entity($new_entity); +	} +	return $new_entity; +} + +/** + * Does an entity exist? + * + * This function checks for the existence of an entity independent of access + * permissions. It is useful for situations when a user cannot access an entity + * and it must be determined whether entity has been deleted or the access level + * has changed. + * + * @param int $guid The GUID of the entity + * + * @return bool + * @since 1.8.0 + */ +function elgg_entity_exists($guid) { +	global $CONFIG; + +	$guid = sanitize_int($guid); + +	$query = "SELECT count(*) as total FROM {$CONFIG->dbprefix}entities WHERE guid = $guid"; +	$result = get_data_row($query); +	if ($result->total == 0) { +		return false; +	} else { +		return true; +	} +} + +/** + * 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', + +		'__ElggBatch'			=> null, +	); + +	$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, $options['__ElggBatch']); +		} else { +			$dt = get_data($query, $options['callback']); +		} + +		if ($dt) { +			// populate entity and metadata caches +			$guids = array(); +			foreach ($dt as $item) { +				// A custom callback could result in items that aren't ElggEntity's, so check for them +				if ($item instanceof ElggEntity) { +					_elgg_cache_entity($item); +					// plugins usually have only settings +					if (!$item instanceof ElggPlugin) { +						$guids[] = $item->guid; +					} +				} +			} +			// @todo Without this, recursive delete fails. See #4568 +			reset($dt); + +			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 + * @param ElggBatch $batch + * @return ElggEntity[] + * + * @access private + * @throws LogicException + */ +function _elgg_fetch_entities_from_sql($sql, ElggBatch $batch = null) { +	static $plugin_subtype; +	if (null === $plugin_subtype) { +		$plugin_subtype = get_subtype_id('object', 'plugin'); +	} + +	// Keys are types, values are columns that, if present, suggest that the secondary +	// table is already JOINed +	$types_to_optimize = array( +		'object' => 'title', +		'user' => 'password', +		'group' => 'name', +	); + +	$rows = get_data($sql); + +	// guids to look up in each type +	$lookup_types = array(); +	// maps GUIDs to the $rows key +	$guid_to_key = array(); + +	if (isset($rows[0]->type, $rows[0]->subtype) +			&& $rows[0]->type === 'object' +			&& $rows[0]->subtype == $plugin_subtype) { +		// Likely the entire resultset is plugins, which have already been optimized +		// to JOIN the secondary table. In this case we allow retrieving from cache, +		// but abandon the extra queries. +		$types_to_optimize = array(); +	} + +	// First pass: use cache where possible, gather GUIDs that we're optimizing +	foreach ($rows as $i => $row) { +		if (empty($row->guid) || empty($row->type)) { +			throw new LogicException('Entity row missing guid or type'); +		} +		if ($entity = _elgg_retrieve_cached_entity($row->guid)) { +			$rows[$i] = $entity; +			continue; +		} +		if (isset($types_to_optimize[$row->type])) { +			// check if row already looks JOINed. +			if (isset($row->{$types_to_optimize[$row->type]})) { +				// Row probably already contains JOINed secondary table. Don't make another query just +				// to pull data that's already there +				continue; +			} +			$lookup_types[$row->type][] = $row->guid; +			$guid_to_key[$row->guid] = $i; +		} +	} +	// Do secondary queries and merge rows +	if ($lookup_types) { +		$dbprefix = elgg_get_config('dbprefix'); + +		foreach ($lookup_types as $type => $guids) { +			$set = "(" . implode(',', $guids) . ")"; +			$sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set"; +			$secondary_rows = get_data($sql); +			if ($secondary_rows) { +				foreach ($secondary_rows as $secondary_row) { +					$key = $guid_to_key[$secondary_row->guid]; +					// cast to arrays to merge then cast back +					$rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row); +				} +			} +		} +	} +	// Second pass to finish conversion +	foreach ($rows as $i => $row) { +		if ($row instanceof ElggEntity) { +			continue; +		} else { +			try { +				$rows[$i] = entity_row_to_elggstar($row); +			} catch (IncompleteEntityException $e) { +				// don't let incomplete entities throw fatal errors +				unset($rows[$i]); + +				// report incompletes to the batch process that spawned this query +				if ($batch) { +					$batch->reportIncompleteEntity($row); +				} +			} +		} +	} +	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 +					if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) { +						// subtype value is 0 +						$subtype_ids[] = ELGG_ENTITIES_NO_VALUE; +					} elseif (!$subtype) { +						// subtype is ignored. +						// this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0 +						continue; +					} else { +						$subtype_id = get_subtype_id($type, $subtype); +						 +						if ($subtype_id) { +							$subtype_ids[] = $subtype_id; +						} else { +							$valid_subtypes_count--; +							elgg_log("Type-subtype '$type:$subtype' does not exist!", 'NOTICE'); +							continue; +						} +					} +				} + +				// return false if we're all invalid subtypes in the only valid type +				if ($valid_subtypes_count <= 0) { +					return FALSE; +				} +			} + +			if (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; + +	$offset_key = isset($options['offset_key']) ? $options['offset_key'] : 'offset'; + +	$defaults = array( +		'offset' => (int) max(get_input($offset_key, 0), 0), +		'limit' => (int) max(get_input('limit', 10), 0), +		'full_view' => TRUE, +		'list_type_toggle' => FALSE, +		'pagination' => TRUE, +	); + +	$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. + * + * @todo document how to pass in array for $subtype + * + * @warning Months are returned in the form YYYYMM. + * + * @param string $type           The type of entity + * @param string $subtype        The subtype of entity + * @param int    $container_guid The container GUID that the entities belong to + * @param int    $site_guid      The site GUID + * @param string $order_by       Order_by SQL order by clause + * + * @return array|false Either an array months as YYYYMM, or false on failure + */ +function 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(); +				_elgg_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, 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])) { +					_elgg_invalidate_cache_for_entity($guid); +				} +				 +				// If memcache is available then delete this entry from the cache +				static $newentity_cache; +				if ((!$newentity_cache) && (is_memcache_available())) { +					$newentity_cache = new ElggMemcache('new_entity_cache'); +				} +				if ($newentity_cache) { +					$newentity_cache->delete($guid); +				} + +				// Delete contained owned and otherwise releated objects (depth first) +				if ($recursive) { +					// Temporary token overriding access controls +					// @todo Do this better. +					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); +				} + +				$entity_disable_override = access_get_show_hidden_status(); +				access_show_hidden_entities(true); +				$ia = elgg_set_ignore_access(true); + +				// Now delete the entity itself +				$entity->deleteMetadata(); +				$entity->deleteOwnedMetadata(); +				$entity->deleteAnnotations(); +				$entity->deleteOwnedAnnotations(); +				$entity->deleteRelationships(); + +				access_show_hidden_entities($entity_disable_override); +				elgg_set_ignore_access($ia); + +				elgg_delete_river(array('subject_guid' => $guid)); +				elgg_delete_river(array('object_guid' => $guid)); +				remove_all_private_settings($guid); + +				$res = delete_data("DELETE from {$CONFIG->dbprefix}entities where guid={$guid}"); +				if ($res) { +					$sub_table = ""; + +					// 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 && ($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) { +		/* @var ElggEntity $entity */ +		$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( +		'site' => 'sites_entity', +		'object' => 'objects_entity', +		'group' => 'groups_entity', +		'user' => 'users_entity' +	); + +	foreach ($tables as $type => $table) { +		delete_data("DELETE FROM {$CONFIG->dbprefix}{$table} +			WHERE guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}entities)"); +		delete_data("DELETE FROM {$CONFIG->dbprefix}entities +			WHERE type = '$type' AND guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}{$table})"); +	} +} + +/** + * Runs unit tests for the 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..ecc894e63 --- /dev/null +++ b/engine/lib/export.php @@ -0,0 +1,223 @@ +<?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 string|false 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) { +	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) { +	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 ElggEntity|false + */ +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. +	$to_be_serialised = null; +	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 string 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 ImportException 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..8323bd3ce --- /dev/null +++ b/engine/lib/extender.php @@ -0,0 +1,249 @@ +<?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. + * @throws ImportException + * @access private + */ +function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) { +	$element = $params['element']; + +	$tmp = NULL; + +	if ($element instanceof ODDMetaData) { +		/* @var ODDMetaData $element */ +		// Recall entity +		$entity_uuid = $element->getAttribute('entity_uuid'); +		$entity = get_entity_from_uuid($entity_uuid); +		if (!$entity) { +			throw new ImportException(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) { +	// @todo Since Elgg 1.0, Elgg has returned false from can_edit_extender() +	// if no user was logged in. This breaks the access override. This is a +	// temporary work around. This function needs to be rewritten in Elgg 1.9  +	if (!elgg_check_access_overrides($user_guid)) { +		if (!elgg_is_logged_in()) { +			return false; +		} +	} + +	$user_guid = (int)$user_guid; +	$user = get_user($user_guid); +	if (!$user) { +		$user = elgg_get_logged_in_user_entity(); +		$user_guid = elgg_get_logged_in_user_guid(); +	} + +	$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_guid) { +		return true; +	} + +	// If the user can edit the entity this is attached to, great! They can edit. +	if (can_edit_entity($extender->entity_guid, $user_guid)) { +		return true; +	} + +	// Trigger plugin hook - note that $user may be null +	$params = array('entity' => $extender->getEntity(), 'user' => $user); +	return elgg_trigger_plugin_hook('permissions_check', $type, $params, false); +} + +/** + * Sets the URL handler for a particular extender type and name. + * It is recommended that you do not call this directly, instead use + * one of the wrapper functions 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..a3c7ba439 --- /dev/null +++ b/engine/lib/filestore.php @@ -0,0 +1,520 @@ +<?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()) { +			$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 string $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 string  $hook   unit_test + * @param string $type   system + * @param mixed  $value  Array of tests + * @param mixed  $params Params + * + * @return array + * @access private + */ +function filestore_test($hook, $type, $value, $params) { +	global $CONFIG; +	$value[] = "{$CONFIG->path}engine/tests/objects/filestore.php"; +	return $value; +} + + +// Register a startup event +elgg_register_event_handler('init', 'system', 'filestore_init', 100); + +// Unit testing +elgg_register_plugin_hook_handler('unit_test', 'system', 'filestore_test'); diff --git a/engine/lib/group.php b/engine/lib/group.php new file mode 100644 index 000000000..6ded8a825 --- /dev/null +++ b/engine/lib/group.php @@ -0,0 +1,341 @@ +<?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, +		'type' => '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( +		'type' => 'group', +		'relationship' => 'member', +		'relationship_guid' => $user_guid, +		'inverse_relationship' => false, +		'limit' => 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..80b0b8766 --- /dev/null +++ b/engine/lib/input.php @@ -0,0 +1,520 @@ +<?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|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. + * + * @todo split this into functions/objects, this is way too big + * + * /livesearch?q=<query> + * + * Other options include: + *     match_on	   string all or array(groups|users|friends) + *     match_owner int    0/1 + *     limit       int    default is 10 + * + * @param array $page + * @return string JSON string is returned and then exit + * @access private + */ +function input_livesearch_page_handler($page) { +	global $CONFIG; + +	// only return results to logged in users. +	if (!$user = 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_where = 'AND e.owner_guid = ' . $user->getGUID(); +	} else { +		$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) { +						// @todo use elgg_get_entities (don't query in a loop!) +						$entity = get_entity($entity->guid); +						/* @var ElggUser $entity */ +						if (!$entity) { +							continue; +						} + +						if (in_array('groups', $match_on)) { +							$value = $entity->guid; +						} else { +							$value = $entity->username; +						} + +						$output = elgg_view_list_item($entity, array( +							'use_hover' => false, +							'class' => 'elgg-autocomplete-item', +						)); + +						$icon = elgg_view_entity_icon($entity, 'tiny', array( +							'use_hover' => false, +						)); + +						$result = array( +							'type' => 'user', +							'name' => $entity->name, +							'desc' => $entity->username, +							'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) { +						// @todo use elgg_get_entities (don't query in a loop!) +						$entity = get_entity($entity->guid); +						/* @var ElggGroup $entity */ +						if (!$entity) { +							continue; +						} + +						$output = elgg_view_list_item($entity, array( +							'use_hover' => false, +							'class' => 'elgg-autocomplete-item', +						)); + +						$icon = elgg_view_entity_icon($entity, 'tiny', array( +							'use_hover' => false, +						)); + +						$result = array( +							'type' => 'group', +							'name' => $entity->name, +							'desc' => strip_tags($entity->description), +							'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) { +						// @todo use elgg_get_entities (don't query in a loop!) +						$entity = get_entity($entity->guid); +						/* @var ElggUser $entity */ +						if (!$entity) { +							continue; +						} + +						$output = elgg_view_list_item($entity, array( +							'use_hover' => false, +							'class' => 'elgg-autocomplete-item', +						)); + +						$icon = elgg_view_entity_icon($entity, 'tiny', array( +							'use_hover' => false, +						)); + +						$result = array( +							'type' => 'user', +							'name' => $entity->name, +							'desc' => $entity->username, +							'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..61ba91ddb --- /dev/null +++ b/engine/lib/languages.php @@ -0,0 +1,354 @@ +<?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 bool 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() { +	$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; +} + +/** + * @access private + */ +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.lang"); +			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 bool success + */ +function register_translations($path, $load_all = false) { +	global $CONFIG; + +	$path = sanitise_filepath($path); + +	// Make a note of this path just incase we need to register this language later +	if (!isset($CONFIG->language_paths)) { +		$CONFIG->language_paths = array(); +	} +	$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. + * + * @todo Better on demand loading based on language_paths array + * + * @return void + */ +function reload_all_translations() { +	global $CONFIG; + +	static $LANG_RELOAD_ALL_RUN; +	if ($LANG_RELOAD_ALL_RUN) { +		return; +	} + +	if ($CONFIG->i18n_loaded_from_cache) { +		$cache = elgg_get_system_cache(); +		$cache_dir = $cache->getVariable("cache_path"); +		$filenames = elgg_get_file_list($cache_dir, array(), array(), array(".lang")); +		foreach ($filenames as $filename) { +			if (preg_match('/([a-z]+)\.[^.]+$/', $filename, $matches)) { +				$language = $matches[1]; +				$data = elgg_load_system_cache("$language.lang"); +				if ($data) { +					add_translation($language, unserialize($data)); +				} +			} +		} +	} else { +		foreach ($CONFIG->language_paths as $path => $dummy) { +			register_translations($path, true); +		} +	} + +	$LANG_RELOAD_ALL_RUN = true; +} + +/** + * Return an array of installed translations as an associative + * array "two letter code" => "native language name". + * + * @return 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; +} diff --git a/engine/lib/location.php b/engine/lib/location.php new file mode 100644 index 000000000..1534c7d7b --- /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; + +	$wheres = array(); +	$wheres[] = "lat_name.string='geo:lat'"; +	$wheres[] = "lat_value.string >= $lat_min"; +	$wheres[] = "lat_value.string <= $lat_max"; +	$wheres[] = "lon_name.string='geo:long'"; +	$wheres[] = "lon_value.string >= $long_min"; +	$wheres[] = "lon_value.string <= $long_max"; + +	$joins = array(); +	$joins[] = "JOIN {$CONFIG->dbprefix}metadata lat on e.guid=lat.entity_guid"; +	$joins[] = "JOIN {$CONFIG->dbprefix}metastrings lat_name on lat.name_id=lat_name.id"; +	$joins[] = "JOIN {$CONFIG->dbprefix}metastrings lat_value on lat.value_id=lat_value.id"; +	$joins[] = "JOIN {$CONFIG->dbprefix}metadata lon on e.guid=lon.entity_guid"; +	$joins[] = "JOIN {$CONFIG->dbprefix}metastrings lon_name on lon.name_id=lon_name.id"; +	$joins[] = "JOIN {$CONFIG->dbprefix}metastrings lon_value on lon.value_id=lon_value.id"; + +	// merge wheres to pass to get_entities() +	if (isset($options['wheres']) && !is_array($options['wheres'])) { +		$options['wheres'] = array($options['wheres']); +	} elseif (!isset($options['wheres'])) { +		$options['wheres'] = array(); +	} +	$options['wheres'] = array_merge($options['wheres'], $wheres); + +	// 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 Options array + * + * @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..68fa69005 --- /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 string $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..79b87e850 --- /dev/null +++ b/engine/lib/memcache.php @@ -0,0 +1,57 @@ +<?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; +} + +/** + * Invalidate an entity in memcache + * + * @param int $entity_guid The GUID of the entity to invalidate + * + * @return void + * @access private + */ +function _elgg_invalidate_memcache_for_entity($entity_guid) { +	static $newentity_cache; +	
 +	if ((!$newentity_cache) && (is_memcache_available())) {
 +		$newentity_cache = new ElggMemcache('new_entity_cache');
 +	} +	
 +	if ($newentity_cache) {
 +		$newentity_cache->delete($entity_guid);
 +	} +}
\ No newline at end of file diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php new file mode 100644 index 000000000..fdb1b85f6 --- /dev/null +++ b/engine/lib/metadata.php @@ -0,0 +1,978 @@ +<?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_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 = "UPDATE {$CONFIG->dbprefix}metadata" +		. " set name_id='$name_id', value_id='$value_id', value_type='$value_type', access_id=$access_id," +		. " owner_guid=$owner_guid where id=$id"; + +	$result = update_data($query); +	if ($result !== false) { + +		elgg_get_metadata_cache()->save($md->entity_guid, $name, $value); + +		// @todo this event tells you the metadata has been updated, but does not +		// let you do anything about it. What is needed is a plugin hook before +		// the update that passes old and new values. +		$obj = elgg_get_metadata_from_id($id); +		elgg_trigger_event('update', 'metadata', $obj); +	} + +	return $result; +} + +/** + * 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 ElggMetadata[]|mixed + * @since 1.8.0 + */ +function elgg_get_metadata(array $options = array()) { + +	// @todo remove support for count shortcut - see #4393 +	// support shortcut of 'count' => true for 'metadata_calculation' => 'count' +	if (isset($options['count']) && $options['count']) { +		$options['metadata_calculation'] = 'count'; +		unset($options['count']); +	} + +	$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. + * + * @param array $options An options array. {@see elgg_get_metadata()} + * @return bool|null true on success, false on failure, null if no metadata to delete. + * @since 1.8.0 + */ +function elgg_delete_metadata(array $options) { +	if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) { +		return false; +	} +	$options['metastring_type'] = 'metadata'; +	$result = elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); + +	// This moved last in case an object's constructor sets metadata. Currently the batch +	// delete process has to create the entity to delete its metadata. See #5214 +	elgg_get_metadata_cache()->invalidateByOptions('delete', $options); + +	return $result; +} + +/** + * Disables metadata based on $options. + * + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return bool|null true on success, false on failure, null if no metadata disabled. + * @since 1.8.0 + */ +function elgg_disable_metadata(array $options) { +	if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) { +		return false; +	} + +	elgg_get_metadata_cache()->invalidateByOptions('disable', $options); +	 +	// if we can see hidden (disabled) we need to use the offset +	// otherwise we risk an infinite loop if there are more than 50 +	$inc_offset = access_get_show_hidden_status(); + +	$options['metastring_type'] = 'metadata'; +	return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset); +} + +/** + * Enables metadata based on $options. + * + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * + * @warning In order to enable metadata, you must first use + * {@link access_show_hidden_entities()}. + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return bool|null true on success, false on failure, null if no metadata enabled. + * @since 1.8.0 + */ +function 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". + *                               If passing "IN" as the operand and a string as the value,  + *                               the value must be a properly quoted and escaped string. + * + * 	metadata_name_value_pairs_operator => NULL|STR The operator to use for combining + *                                        (name = value) OPERATOR (name = value); default AND + * + * 	metadata_case_sensitive => BOOL Overall Case sensitive + * + *  order_by_metadata => NULL|ARR array( + *                                      'name' => 'metadata_text1', + *                                      'direction' => ASC|DESC, + *                                      'as' => text|integer + *                                     ) + *                                Also supports array('name' => 'metadata_text1') + * + *  metadata_owner_guids => NULL|ARR guids for metadata owners + * + * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors. + * @since 1.7.0 + */ +function elgg_get_entities_from_metadata(array $options = array()) { +	$defaults = array( +		'metadata_names'                     => ELGG_ENTITIES_ANY_VALUE, +		'metadata_values'                    => ELGG_ENTITIES_ANY_VALUE, +		'metadata_name_value_pairs'          => ELGG_ENTITIES_ANY_VALUE, + +		'metadata_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_bool($pair['value'])) { +				$value = (int) $pair['value']; +			} else if (is_array($pair['value'])) { +				$values_array = array(); + +				foreach ($pair['value'] as $pair_value) { +					if (is_numeric($pair_value)) { +						$values_array[] = sanitise_string($pair_value); +					} else { +						$values_array[] = "'" . sanitise_string($pair_value) . "'"; +					} +				} + +				if ($values_array) { +					$value = '(' . implode(', ', $values_array) . ')'; +				} + +				// @todo allow support for non IN operands with array of values. +				// will have to do more silly joins. +				$operand = 'IN'; +			} else if ($trimmed_operand == 'in') { +				$value = "({$pair['value']})"; +			} else { +				$value = "'" . sanitise_string($pair['value']) . "'"; +			} + +			$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'); +		$ar = array_map('strip_tags', $ar); +		return $ar; +	} +	return false; +} + +/** + * Takes a metadata array (which has all kinds of properties) + * and turns it into a simple array of strings + * + * @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 + * @return void + */ +function elgg_invalidate_metadata_cache($action, array $options) { +	// remove as little as possible, optimizing for common cases +	$cache = elgg_get_metadata_cache(); +	if (empty($options['guid'])) { +		// safest to clear everything unless we want to make this even more complex :( +		$cache->flush(); +	} else { +		if (empty($options['metadata_name'])) { +			// safest to clear the whole entity +			$cache->clear($options['guid']); +		} else { +			switch ($action) { +				case 'delete': +					$cache->markEmpty($options['guid'], $options['metadata_name']); +					break; +				default: +					$cache->markUnknown($options['guid'], $options['metadata_name']); +			} +		} +	} +} + +/** Register the hook */ +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..57d876c06 --- /dev/null +++ b/engine/lib/metastrings.php @@ -0,0 +1,903 @@ +<?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); +	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']; + +	// For performance reasons we don't want the joins required for metadata / annotations +	// unless we're going through one of their callbacks. +	// this means we expect the functions passing different callbacks to pass their required joins. +	// If we're doing a calculation +	$custom_callback = ($options['callback'] == 'row_to_elggmetadata' +						|| $options['callback'] == 'row_to_elggannotation'); +	$is_calculation = $options['metastring_calculation'] ? true : false; +	 +	if ($custom_callback || $is_calculation) { +		$joins[] = "JOIN {$db_prefix}metastrings n on n_table.name_id = n.id"; +		$joins[] = "JOIN {$db_prefix}metastrings v on n_table.value_id = v.id"; + +		$selects[] = 'n.string as name'; +		$selects[] = 'v.string as value'; +	} + +	foreach ($joins as $i => $join) { +		if ($join === FALSE) { +			return FALSE; +		} elseif (empty($join)) { +			unset($joins[$i]); +		} +	} + +	// metastrings +	$metastring_clauses = elgg_get_metastring_sql('n_table', $options['metastring_names'], +		$options['metastring_values'], null, $options['metastring_ids'], +		$options['metastring_case_sensitive']); + +	if ($metastring_clauses) { +		$wheres = array_merge($wheres, $metastring_clauses['wheres']); +		$joins = array_merge($joins, $metastring_clauses['joins']); +	} else { +		$wheres[] = get_access_sql_suffix('n_table'); +	} + +	if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) { +		$selects = array_unique($selects); +		// evalutate selects +		$select_str = ''; +		if ($selects) { +			foreach ($selects as $select) { +				$select_str .= ", $select"; +			} +		} + +		$query = "SELECT DISTINCT n_table.*{$select_str} FROM {$db_prefix}$type n_table"; +	} elseif ($options['count']) { +		// count is over the entities +		$query = "SELECT count(DISTINCT e.guid) as calculation FROM {$db_prefix}$type n_table"; +	} else { +		$query = "SELECT {$options['metastring_calculation']}(v.string) as calculation FROM {$db_prefix}$type n_table"; +	} + +	// remove identical join clauses +	$joins = array_unique($joins); + +	// add joins +	foreach ($joins as $j) { +		$query .= " $j "; +	} + +	// add wheres +	$query .= ' WHERE '; + +	foreach ($wheres as $w) { +		$query .= " $w AND "; +	} + +	// Add access controls +	$query .= get_access_sql_suffix('e'); + +	// reverse order by +	if (isset($options['reverse_order_by']) && $options['reverse_order_by']) { +		$options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by'], +			$defaults['order_by']); +	} + +	if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) { +		if (isset($options['group_by'])) { +			$options['group_by'] = sanitise_string($options['group_by']); +			$query .= " GROUP BY {$options['group_by']}"; +		} + +		if (isset($options['order_by']) && $options['order_by']) { +			$options['order_by'] = sanitise_string($options['order_by']); +			$query .= " ORDER BY {$options['order_by']}, n_table.id"; +		} + +		if ($options['limit']) { +			$limit = sanitise_int($options['limit']); +			$offset = sanitise_int($options['offset'], false); +			$query .= " LIMIT $offset, $limit"; +		} + +		$dt = get_data($query, $options['callback']); +		return $dt; +	} else { +		$result = get_data_row($query); +		return $result->calculation; +	} +} + +/** + * Returns an array of joins and wheres for use in metastrings. + * + * @note The $pairs is reserved for name/value pairs if we want to implement those. + * + * @param string $table          The annotation or metadata table name or alias + * @param array  $names          An array of names + * @param array  $values         An array of values + * @param array  $pairs          Name / value pairs. Not currently used. + * @param array  $ids            Metastring IDs + * @param bool   $case_sensitive Should name and values be case sensitive? + * + * @return array + * @access private + */ +function elgg_get_metastring_sql($table, $names = null, $values = null, +	$pairs = null, $ids = null, $case_sensitive = false) { + +	if ((!$names && $names !== 0) +		&& (!$values && $values !== 0) +		&& !$ids +		&& (!$pairs && $pairs !== 0)) { + +		return array(); +	} + +	$db_prefix = elgg_get_config('dbprefix'); + +	// binary forces byte-to-byte comparision of strings, making +	// it case- and diacritical-mark- sensitive. +	// only supported on values. +	$binary = ($case_sensitive) ? ' BINARY ' : ''; + +	$return = array ( +		'joins' => array (), +		'wheres' => array() +	); + +	$wheres = array(); + +	// get names wheres and joins +	$names_where = ''; +	if ($names !== NULL) { +		if (!is_array($names)) { +			$names = array($names); +		} + +		$sanitised_names = array(); +		foreach ($names as $name) { +			// normalise to 0. +			if (!$name) { +				$name = '0'; +			} +			$sanitised_names[] = '\'' . sanitise_string($name) . '\''; +		} + +		if ($names_str = implode(',', $sanitised_names)) { +			$return['joins'][] = "JOIN {$db_prefix}metastrings msn on $table.name_id = msn.id"; +			$names_where = "(msn.string IN ($names_str))"; +		} +	} + +	// get values wheres and joins +	$values_where = ''; +	if ($values !== NULL) { +		if (!is_array($values)) { +			$values = array($values); +		} + +		$sanitised_values = array(); +		foreach ($values as $value) { +			// normalize to 0 +			if (!$value) { +				$value = 0; +			} +			$sanitised_values[] = '\'' . sanitise_string($value) . '\''; +		} + +		if ($values_str = implode(',', $sanitised_values)) { +			$return['joins'][] = "JOIN {$db_prefix}metastrings msv on $table.value_id = msv.id"; +			$values_where = "({$binary}msv.string IN ($values_str))"; +		} +	} + +	if ($ids !== NULL) { +		if (!is_array($ids)) { +			$ids = array($ids); +		} + +		$ids_str = implode(',', $ids); + +		if ($ids_str) { +			$wheres[] = "n_table.id IN ($ids_str)"; +		} +	} + +	if ($names_where && $values_where) { +		$wheres[] = "($names_where AND $values_where)"; +	} elseif ($names_where) { +		$wheres[] = $names_where; +	} elseif ($values_where) { +		$wheres[] = $values_where; +	} + +	$wheres[] = get_access_sql_suffix($table); + +	if ($where = implode(' AND ', $wheres)) { +		$return['wheres'][] = "($where)"; +	} + +	return $return; +} + +/** + * Normalizes metadata / annotation option names to their corresponding metastrings name. + * + * @param array $options An options array + * @since 1.8.0 + * @return array + * @access private + */ +function elgg_normalize_metastrings_options(array $options = array()) { + +	// support either metastrings_type or metastring_type +	// because I've made this mistake many times and hunting it down is a pain... +	$type = elgg_extract('metastring_type', $options, null); +	$type = elgg_extract('metastrings_type', $options, $type); + +	$options['metastring_type'] = $type; + +	// support annotation_ and annotations_ because they're way too easy to confuse +	$prefixes = array('metadata_', 'annotation_', 'annotations_'); + +	// map the metadata_* options to metastring_* options +	$map = array( +		'names'					=>	'metastring_names', +		'values'				=>	'metastring_values', +		'case_sensitive'		=>	'metastring_case_sensitive', +		'owner_guids'			=>	'metastring_owner_guids', +		'created_time_lower'	=>	'metastring_created_time_lower', +		'created_time_upper'	=>	'metastring_created_time_upper', +		'calculation'			=>	'metastring_calculation', +		'ids'					=>	'metastring_ids' +	); + +	foreach ($prefixes as $prefix) { +		$singulars = array("{$prefix}name", "{$prefix}value", "{$prefix}owner_guid", "{$prefix}id"); +		$options = elgg_normalise_plural_options_array($options, $singulars); + +		foreach ($map as $specific => $normalized) { +			$key = $prefix . $specific; +			if (isset($options[$key])) { +				$options[$normalized] = $options[$key]; +			} +		} +	} + +	return $options; +} + +/** + * Enables or disables a metastrings-based object by its id. + * + * @warning To enable disabled metastrings you must first use + * {@link access_show_hidden_entities()}. + * + * @param int    $id      The object's ID + * @param string $enabled Value to set to: yes or no + * @param string $type    The type of table to use: metadata or annotations + * + * @return bool + * @throws InvalidParameterException + * @since 1.8.0 + * @access private + */ +function elgg_set_metastring_based_object_enabled_by_id($id, $enabled, $type) { +	$id = (int)$id; +	$db_prefix = elgg_get_config('dbprefix'); + +	$object = elgg_get_metastring_based_object_from_id($id, $type); + +	switch($type) { +		case 'annotation': +		case 'annotations': +			$table = "{$db_prefix}annotations"; +			break; + +		case 'metadata': +			$table = "{$db_prefix}metadata"; +			break; +	} + +	if ($enabled === 'yes' || $enabled === 1 || $enabled === true) { +		$enabled = 'yes'; +		$event = 'enable'; +	} elseif ($enabled === 'no' || $enabled === 0 || $enabled === false) { +		$enabled = 'no'; +		$event = 'disable'; +	} else { +		return false; +	} + +	$return = false; + +	if ($object) { +		// don't set it if it's already set. +		if ($object->enabled == $enabled) { +			$return = false; +		} elseif ($object->canEdit() && (elgg_trigger_event($event, $type, $object))) { +			$return = update_data("UPDATE $table SET enabled = '$enabled' where id = $id"); +		} +	} + +	return $return; +} + +/** + * Runs metastrings-based objects found using $options through $callback + * + * @warning Unlike elgg_get_metastring_based_objects() this will not accept an + * empty options array! + * + * @warning This returns null on no ops. + * + * @param array  $options    An options array. {@See elgg_get_metastring_based_objects()} + * @param string $callback   The callback to pass each result through + * @param bool   $inc_offset Increment the offset? Pass false for callbacks that delete / disable + * + * @return bool|null true on success, false on failure, null if no objects are found. + * @since 1.8.0 + * @access private + */ +function elgg_batch_metastring_based_objects(array $options, $callback, $inc_offset = true) { +	if (!$options || !is_array($options)) { +		return false; +	} + +	$batch = new ElggBatch('elgg_get_metastring_based_objects', $options, $callback, 50, $inc_offset); +	return $batch->callbackResult; +} + +/** + * Returns a singular metastring-based object by its ID. + * + * @param int    $id   The metastring-based object's ID + * @param string $type The type: annotation or metadata + * @return ElggMetadata|ElggAnnotation + * + * @since 1.8.0 + * @access private + */ +function elgg_get_metastring_based_object_from_id($id, $type) { +	$id = (int)$id; +	if (!$id) { +		return false; +	} + +	$options = array( +		'metastring_type' => $type, +		'metastring_id' => $id +	); + +	$obj = elgg_get_metastring_based_objects($options); + +	if ($obj && count($obj) == 1) { +		return $obj[0]; +	} + +	return false; +} + +/** + * Deletes a metastring-based object by its id + * + * @param int    $id   The object's ID + * @param string $type The object's metastring type: annotation or metadata + * @return bool + * + * @since 1.8.0 + * @access private + */ +function elgg_delete_metastring_based_object_by_id($id, $type) { +	$id = (int)$id; +	$db_prefix = elgg_get_config('dbprefix'); + +	switch ($type) { +		case 'annotation': +		case 'annotations': +			$type = 'annotations'; +			break; + +		case 'metadata': +			$type = 'metadata'; +			break; + +		default: +			return false; +	} + +	$obj = elgg_get_metastring_based_object_from_id($id, $type); +	$table = $db_prefix . $type; + +	if ($obj) { +		// Tidy up if memcache is enabled. +		// @todo only metadata is supported +		if ($type == 'metadata') { +			static $metabyname_memcache; +			if ((!$metabyname_memcache) && (is_memcache_available())) { +				$metabyname_memcache = new ElggMemcache('metabyname_memcache'); +			} + +			if ($metabyname_memcache) { +				// @todo why name_id? is that even populated? +				$metabyname_memcache->delete("{$obj->entity_guid}:{$obj->name_id}"); +			} +		} + +		if (($obj->canEdit()) && (elgg_trigger_event('delete', $type, $obj))) { +			return (bool)delete_data("DELETE from $table where id=$id"); +		} +	} + +	return false; +} + +/** + * Entities interface helpers + */ + +/** + * Returns options to pass to elgg_get_entities() for metastrings operations. + * + * @param string $type    Metastring type: annotations or metadata + * @param array  $options Options + * + * @return array + * @since 1.7.0 + * @access private + */ +function elgg_entities_get_metastrings_options($type, $options) { +	$valid_types = array('metadata', 'annotation'); +	if (!in_array($type, $valid_types)) { +		return FALSE; +	} + +	// the options for annotations are singular (annotation_name) but the table +	// is plural (elgg_annotations) so rewrite for the table name. +	$n_table = ($type == 'annotation') ? 'annotations' : $type; + +	$singulars = array("{$type}_name", "{$type}_value", +		"{$type}_name_value_pair", "{$type}_owner_guid"); +	$options = elgg_normalise_plural_options_array($options, $singulars); + +	$clauses = elgg_get_entity_metadata_where_sql('e', $n_table, $options["{$type}_names"], +		$options["{$type}_values"], $options["{$type}_name_value_pairs"], +		$options["{$type}_name_value_pairs_operator"], $options["{$type}_case_sensitive"], +		$options["order_by_{$type}"], $options["{$type}_owner_guids"]); + +	if ($clauses) { +		// merge wheres to pass to get_entities() +		if (isset($options['wheres']) && !is_array($options['wheres'])) { +			$options['wheres'] = array($options['wheres']); +		} elseif (!isset($options['wheres'])) { +			$options['wheres'] = array(); +		} + +		$options['wheres'] = array_merge($options['wheres'], $clauses['wheres']); + +		// merge joins to pass to get_entities() +		if (isset($options['joins']) && !is_array($options['joins'])) { +			$options['joins'] = array($options['joins']); +		} elseif (!isset($options['joins'])) { +			$options['joins'] = array(); +		} + +		$options['joins'] = array_merge($options['joins'], $clauses['joins']); + +		if ($clauses['orders']) { +			$order_by_metadata = implode(", ", $clauses['orders']); +			if (isset($options['order_by']) && $options['order_by']) { +				$options['order_by'] = "$order_by_metadata, {$options['order_by']}"; +			} else { +				$options['order_by'] = "$order_by_metadata, e.time_created DESC"; +			} +		} +	} + +	return $options; +} + +// unit testing +elgg_register_plugin_hook_handler('unit_test', 'system', 'metastrings_test'); + +/** + * Metadata unit test + * + * @param string $hook   unit_test + * @param string $type   system + * @param mixed  $value  Array of other tests + * @param mixed  $params Params + * + * @return array + * @access private + */ +function metastrings_test($hook, $type, $value, $params) { +	global $CONFIG; +	$value[] = $CONFIG->path . 'engine/tests/api/metastrings.php'; +	return $value; +} diff --git a/engine/lib/navigation.php b/engine/lib/navigation.php new file mode 100644 index 000000000..ab9cc05e8 --- /dev/null +++ b/engine/lib/navigation.php @@ -0,0 +1,527 @@ +<?php +/** + * Elgg navigation library + * Functions for managing menus and other navigational elements + * + * Breadcrumbs + * Elgg uses a breadcrumb stack. The page handlers (controllers in MVC terms) + * push the breadcrumb links onto the stack. @see elgg_push_breadcrumb() + * + * + * Pagination + * Automatically handled by Elgg when using elgg_list_entities* functions. + * @see elgg_list_entities() + * + * + * Tabs + * @see navigation/tabs view + * + * + * Menus + * Elgg uses a single interface to manage its menus. Menu items are added with + * {@link elgg_register_menu_item()}. This is generally used for menus that + * appear only once per page. For dynamic menus (such as the hover + * menu for user's avatar), a plugin hook is emitted when the menu is being + * created. The hook is 'register', 'menu:<menu_name>'. For more details on this, + * @see elgg_view_menu(). + * + * Menus supported by the Elgg core + * Standard menus: + *     site   Site navigation shown on every page. + *     page   Page menu usually shown in a sidebar. Uses Elgg's context. + *     topbar Topbar menu shown on every page. The default has two sections. + *     footer Like the topbar but in the footer. + *     extras Links about content on the page. The RSS link is added to this. + * + * Dynamic menus (also called just-in-time menus): + *     user_hover  Avatar hover menu. The user entity is passed as a parameter. + *     entity      The set of links shown in the summary of an entity. + *     river       Links shown on river items. + *     owner_block Links shown for a user or group in their owner block. + *     filter      The tab filter for content (all, mine, friends) + *     title       The buttons shown next to a content title. + *     long-text   The links shown above the input/longtext view. + * + * @package Elgg.Core + * @subpackage Navigation + */ + +/** + * Register an item for an Elgg menu + * + * @warning Generally you should not use this in response to the plugin hook: + * 'register', 'menu:<menu_name>'. If you do, you may end up with many incorrect + * links on a dynamic menu. + * + * @warning A menu item's name must be unique per menu. If more than one menu + * item with the same name are registered, the last menu item takes priority. + * + * @see elgg_view_menu() for the plugin hooks available for modifying a menu as + * it is being rendered. + * + * @param string $menu_name The name of the menu: site, page, userhover, + *                          userprofile, groupprofile, or any custom menu + * @param mixed  $menu_item A ElggMenuItem object or an array of options in format: + *                          name        => STR  Menu item identifier (required) + *                          text        => STR  Menu item display text (required) + *                          href        => STR  Menu item URL (required) (false for non-links. + *                                              @warning If you disable the href the <a> tag will + *                                              not appear, so the link_class will not apply. If you + *                                              put <a> tags in manually through the 'text' option + *                                              the default CSS selector .elgg-menu-$menu > li > a + *                                              may affect formatting. Wrap in a <span> if it does.) + *                          contexts    => ARR  Page context strings + *                          section     => STR  Menu section identifier + *                          title       => STR  Menu item tooltip + *                          selected    => BOOL Is this menu item currently selected + *                          parent_name => STR  Identifier of the parent menu item + *                          link_class  => STR  A class or classes for the <a> tag + *                          item_class  => STR  A class or classes for the <li> tag + * + *                          Additional options that the view output/url takes can be + *							passed in the array. If the 'confirm' key is passed, the + *							menu link uses the 'output/confirmlink' view. Custom + *							options can be added by using the 'data' key with the + *							value being an associative array. + * + * @return bool + * @since 1.8.0 + */ +function elgg_register_menu_item($menu_name, $menu_item) { +	global $CONFIG; + +	if (!isset($CONFIG->menus[$menu_name])) { +		$CONFIG->menus[$menu_name] = array(); +	} + +	if (is_array($menu_item)) { +		$item = ElggMenuItem::factory($menu_item); +		if (!$item) { +			elgg_log("Unable to add menu item '{$menu_item['name']}' to '$menu_name' menu", 'WARNING'); +			elgg_log(print_r($menu_item, true), 'DEBUG'); +			return false; +		} +	} else { +		$item = $menu_item; +	} + +	$CONFIG->menus[$menu_name][] = $item; +	return true; +} + +/** + * Remove an item from a menu + * + * @param string $menu_name The name of the menu + * @param string $item_name The unique identifier for this menu item + * + * @return bool + * @since 1.8.0 + */ +function elgg_unregister_menu_item($menu_name, $item_name) { +	global $CONFIG; + +	if (!isset($CONFIG->menus[$menu_name])) { +		return false; +	} + +	foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) { +		/* @var ElggMenuItem $menu_object */ +		if ($menu_object->getName() == $item_name) { +			unset($CONFIG->menus[$menu_name][$index]); +			return true; +		} +	} + +	return false; +} + +/** + * Check if a menu item has been registered + * + * @param string $menu_name The name of the menu + * @param string $item_name The unique identifier for this menu item + *  + * @return bool + * @since 1.8.0 + */ +function elgg_is_menu_item_registered($menu_name, $item_name) { +	global $CONFIG; + +	if (!isset($CONFIG->menus[$menu_name])) { +		return false; +	} + +	foreach ($CONFIG->menus[$menu_name] as $menu_object) { +		/* @var ElggMenuItem $menu_object */ +		if ($menu_object->getName() == $item_name) { +			return true; +		} +	} + +	return false; +} + +/** + * Convenience function for registering a button to title menu + * + * The URL must be $handler/$name/$guid where $guid is the guid of the page owner. + * The label of the button is "$handler:$name" so that must be defined in a + * language file. + * + * This is used primarily to support adding an add content button + * + * @param string $handler The handler to use or null to autodetect from context + * @param string $name    Name of the button + * @return void + * @since 1.8.0 + */ +function elgg_register_title_button($handler = null, $name = 'add') { +	if (elgg_is_logged_in()) { + +		if (!$handler) { +			$handler = elgg_get_context(); +		} + +		$owner = elgg_get_page_owner_entity(); +		if (!$owner) { +			// no owns the page so this is probably an all site list page +			$owner = elgg_get_logged_in_user_entity(); +		} +		if ($owner && $owner->canWriteToContainer()) { +			$guid = $owner->getGUID(); +			elgg_register_menu_item('title', array( +				'name' => $name, +				'href' => "$handler/$name/$guid", +				'text' => elgg_echo("$handler:$name"), +				'link_class' => 'elgg-button elgg-button-action', +			)); +		} +	} +} + +/** + * Adds a breadcrumb to the breadcrumbs stack. + * + * @param string $title The title to display + * @param string $link  Optional. The link for the title. + * + * @return void + * @since 1.8.0 + * + * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs + */ +function elgg_push_breadcrumb($title, $link = NULL) { +	global $CONFIG; +	if (!isset($CONFIG->breadcrumbs)) { +		$CONFIG->breadcrumbs = array(); +	} + +	// avoid key collisions. +	$CONFIG->breadcrumbs[] = array('title' => elgg_get_excerpt($title, 100), 'link' => $link); +} + +/** + * Removes last breadcrumb entry. + * + * @return array popped item. + * @since 1.8.0 + * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs + */ +function elgg_pop_breadcrumb() { +	global $CONFIG; + +	if (is_array($CONFIG->breadcrumbs)) { +		return array_pop($CONFIG->breadcrumbs); +	} + +	return FALSE; +} + +/** + * Returns all breadcrumbs as an array of array('title' => 'Readable Title', 'link' => 'URL') + * + * @return array Breadcrumbs + * @since 1.8.0 + * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs + */ +function elgg_get_breadcrumbs() { +	global $CONFIG; + +	if (isset($CONFIG->breadcrumbs) && is_array($CONFIG->breadcrumbs)) { +		return $CONFIG->breadcrumbs; +	} + +	return array(); +} + +/** + * Set up the site menu + * + * Handles default, featured, and custom menu items + * + * @param string $hook + * @param string $type + * @param array $return Menu array + * @param array $params + * @return array + * @access private + */ +function elgg_site_menu_setup($hook, $type, $return, $params) { + +	$featured_menu_names = elgg_get_config('site_featured_menu_names'); +	$custom_menu_items = elgg_get_config('site_custom_menu_items'); +	if ($featured_menu_names || $custom_menu_items) { +		// we have featured or custom menu items + +		$registered = $return['default']; + +		// set up featured menu items +		$featured = array(); +		foreach ($featured_menu_names as $name) { +			foreach ($registered as $index => $item) { +				if ($item->getName() == $name) { +					$featured[] = $item; +					unset($registered[$index]); +				} +			} +		} + +		// add custom menu items +		$n = 1; +		foreach ($custom_menu_items as $title => $url) { +			$item = new ElggMenuItem("custom$n", $title, $url); +			$featured[] = $item; +			$n++; +		} + +		$return['default'] = $featured; +		if (count($registered) > 0) { +			$return['more'] = $registered; +		} +	} else { +		// no featured menu items set +		$max_display_items = 5; + +		// the first n are shown, rest added to more list +		// if only one item on more menu, stick it with the rest +		$num_menu_items = count($return['default']); +		if ($num_menu_items > ($max_display_items + 1)) { +			$return['more'] = array_splice($return['default'], $max_display_items); +		} +	} +	 +	// check if we have anything selected +	$selected = false; +	foreach ($return as $section) { +		foreach ($section as $item) { +			if ($item->getSelected()) { +				$selected = true; +				break 2; +			} +		} +	} +	 +	if (!$selected) { +		// nothing selected, match name to context or match url +		$current_url = current_page_url(); +		foreach ($return as $section_name => $section) { +			foreach ($section as $key => $item) { +				// only highlight internal links +				if (strpos($item->getHref(), elgg_get_site_url()) === 0) { +					if ($item->getName() == elgg_get_context()) { +						$return[$section_name][$key]->setSelected(true); +						break 2; +					} +					if ($item->getHref() == $current_url) { +						$return[$section_name][$key]->setSelected(true); +						break 2; +					} +				} +			} +		} +	} + +	return $return; +} + +/** + * Add the comment and like links to river actions menu + * @access private + */ +function elgg_river_menu_setup($hook, $type, $return, $params) { +	if (elgg_is_logged_in()) { +		$item = $params['item']; +		/* @var ElggRiverItem $item */ +		$object = $item->getObjectEntity(); +		// comments and non-objects cannot be commented on or liked +		if (!elgg_in_context('widgets') && $item->annotation_id == 0) { +			// comments +			if ($object->canComment()) { +				$options = array( +					'name' => 'comment', +					'href' => "#comments-add-$object->guid", +					'text' => elgg_view_icon('speech-bubble'), +					'title' => elgg_echo('comment:this'), +					'rel' => 'toggle', +					'priority' => 50, +				); +				$return[] = ElggMenuItem::factory($options); +			} +		} +		 +		if (elgg_is_admin_logged_in()) { +			$options = array( +				'name' => 'delete', +				'href' => elgg_add_action_tokens_to_url("action/river/delete?id=$item->id"), +				'text' => elgg_view_icon('delete'), +				'title' => elgg_echo('delete'), +				'confirm' => elgg_echo('deleteconfirm'), +				'priority' => 200, +			); +			$return[] = ElggMenuItem::factory($options); +		} +	} + +	return $return; +} + +/** + * Entity menu is list of links and info on any entity + * @access private + */ +function elgg_entity_menu_setup($hook, $type, $return, $params) { +	if (elgg_in_context('widgets')) { +		return $return; +	} +	 +	$entity = $params['entity']; +	/* @var ElggEntity $entity */ +	$handler = elgg_extract('handler', $params, false); + +	// access +	$access = elgg_view('output/access', array('entity' => $entity)); +	$options = array( +		'name' => 'access', +		'text' => $access, +		'href' => false, +		'priority' => 100, +	); +	$return[] = ElggMenuItem::factory($options); + +	if ($entity->canEdit() && $handler) { +		// edit link +		$options = array( +			'name' => 'edit', +			'text' => elgg_echo('edit'), +			'title' => elgg_echo('edit:this'), +			'href' => "$handler/edit/{$entity->getGUID()}", +			'priority' => 200, +		); +		$return[] = ElggMenuItem::factory($options); + +		// delete link +		$options = array( +			'name' => 'delete', +			'text' => elgg_view_icon('delete'), +			'title' => elgg_echo('delete:this'), +			'href' => "action/$handler/delete?guid={$entity->getGUID()}", +			'confirm' => elgg_echo('deleteconfirm'), +			'priority' => 300, +		); +		$return[] = ElggMenuItem::factory($options); +	} + +	return $return; +} + +/** + * Widget menu is a set of widget controls + * @access private + */ +function elgg_widget_menu_setup($hook, $type, $return, $params) { + +	$widget = $params['entity']; +	/* @var ElggWidget $widget */ +	$show_edit = elgg_extract('show_edit', $params, true); + +	$collapse = array( +		'name' => 'collapse', +		'text' => ' ', +		'href' => "#elgg-widget-content-$widget->guid", +		'class' => 'elgg-widget-collapse-button', +		'rel' => 'toggle', +		'priority' => 1 +	); +	$return[] = ElggMenuItem::factory($collapse); + +	if ($widget->canEdit()) { +		$delete = array( +			'name' => 'delete', +			'text' => elgg_view_icon('delete-alt'), +			'title' => elgg_echo('widget:delete', array($widget->getTitle())), +			'href' => "action/widgets/delete?widget_guid=$widget->guid", +			'is_action' => true, +			'class' => 'elgg-widget-delete-button', +			'id' => "elgg-widget-delete-button-$widget->guid", +			'priority' => 900 +		); +		$return[] = ElggMenuItem::factory($delete); + +		if ($show_edit) { +			$edit = array( +				'name' => 'settings', +				'text' => elgg_view_icon('settings-alt'), +				'title' => elgg_echo('widget:edit'), +				'href' => "#widget-edit-$widget->guid", +				'class' => "elgg-widget-edit-button", +				'rel' => 'toggle', +				'priority' => 800, +			); +			$return[] = ElggMenuItem::factory($edit); +		} +	} + +	return $return; +} + +/** + * Adds a delete link to "generic_comment" annotations + * @access private + */ +function elgg_annotation_menu_setup($hook, $type, $return, $params) { +	$annotation = $params['annotation']; +	/* @var ElggAnnotation $annotation */ + +	if ($annotation->name == 'generic_comment' && $annotation->canEdit()) { +		$url = elgg_http_add_url_query_elements('action/comments/delete', array( +			'annotation_id' => $annotation->id, +		)); + +		$options = array( +			'name' => 'delete', +			'href' => $url, +			'text' => "<span class=\"elgg-icon elgg-icon-delete\"></span>", +			'confirm' => elgg_echo('deleteconfirm'), +			'encode_text' => false +		); +		$return[] = ElggMenuItem::factory($options); +	} + +	return $return; +} + + +/** + * Navigation initialization + * @access private + */ +function elgg_nav_init() { +	elgg_register_plugin_hook_handler('prepare', 'menu:site', 'elgg_site_menu_setup'); +	elgg_register_plugin_hook_handler('register', 'menu:river', 'elgg_river_menu_setup'); +	elgg_register_plugin_hook_handler('register', 'menu:entity', 'elgg_entity_menu_setup'); +	elgg_register_plugin_hook_handler('register', 'menu:widget', 'elgg_widget_menu_setup'); +	elgg_register_plugin_hook_handler('register', 'menu:annotation', 'elgg_annotation_menu_setup'); +} + +elgg_register_event_handler('init', 'system', 'elgg_nav_init'); diff --git a/engine/lib/notification.php b/engine/lib/notification.php new file mode 100644 index 000000000..be0c359d4 --- /dev/null +++ b/engine/lib/notification.php @@ -0,0 +1,536 @@ +<?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; + +	// 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 = get_user_notification_settings($guid); +				$methods = array(); +				// $tmp may be false. don't cast +				if (is_object($tmp)) { +					foreach ($tmp as $k => $v) { +						// Add method if method is turned on for user! +						if ($v) { +							$methods[] = $k; +						} +					} +				} +			} + +			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|false + */ +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: there should be a better way now that metadata is cached. E.g. just query for MD names, then +	// query user object directly +	$all_metadata = elgg_get_metadata(array( +		'guid' => $user_guid, +		'limit' => 0 +	)); +	if ($all_metadata) { +		$prefix = "notification:method:"; +		$return = new stdClass; + +		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 + * @throws NotificationException + * @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 = elgg_get_site_entity(); +	// 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 + * @throws NotificationException + * @since 1.7.2 + */ +function elgg_send_email($from, $to, $subject, $body, array $params = NULL) { +	global $CONFIG; + +	if (!$from) { +		$msg = elgg_echo('NotificationException:MissingParameter', array('from')); +		throw new NotificationException($msg); +	} + +	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); +	// this is because Elgg encodes everything and matches what is done with body +	$subject = html_entity_decode($subject, ENT_COMPAT, 'UTF-8'); // Decode any html entities +	if (is_callable('mb_encode_mimeheader')) { +		$subject = mb_encode_mimeheader($subject, "UTF-8", "B"); +	} + +	// 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 bool 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 bool 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 bool + * @access private + */ +function object_notifications($event, $object_type, $object) { +	// We only want to trigger notification events for ElggEntities +	if ($object instanceof ElggEntity) { +		/* @var ElggEntity $object */ + +		// Get config data +		global $CONFIG, $SESSION, $NOTIFICATION_HANDLERS; + +		$hookresult = 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, +					'type' => 'user', +					'limit' => false +				)); +				/* @var ElggUser[] $interested_users */ + +				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..ff3cc733f --- /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, +		'type' => 'site', +		'limit' => $limit, +		'offset' => $offset, +	)); +} + +/** + * Runs unit tests for ElggObject + * + * @param string  $hook   unit_test + * @param string $type   system + * @param mixed  $value  Array of tests + * @param mixed  $params Params + * + * @return array + * @access private + */ +function objects_test($hook, $type, $value, $params) { +	global $CONFIG; +	$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..7d635a295 --- /dev/null +++ b/engine/lib/opendd.php @@ -0,0 +1,109 @@ +<?php +/** + * OpenDD PHP Library. + * + * @package Elgg.Core + * @subpackage ODD + * @version 0.4 + */ + +// @codingStandardsIgnoreStart + +/** + * Attempt to construct an ODD object out of a XmlElement or sub-elements. + * + * @param XmlElement $element The element(s) + * + * @return mixed An ODD object if the element can be handled, or false. + * @access private + */ +function ODD_factory (XmlElement $element) { +	$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"; +} + +// @codingStandardsIgnoreEnd diff --git a/engine/lib/output.php b/engine/lib/output.php new file mode 100644 index 000000000..de4f911fb --- /dev/null +++ b/engine/lib/output.php @@ -0,0 +1,469 @@ +<?php +/** + * Output functions + * Processing text for output such as pulling out URLs and extracting excerpts + * + * @package Elgg + * @subpackage Core + */ + +/** + * Takes a string and turns any URLs into formatted links + * + * @param string $text The input string + * + * @return string The output string with formatted links + */ +function parse_urls($text) { + +	// URI specification: http://www.ietf.org/rfc/rfc3986.txt +	// This varies from the specification in the following ways: +	//  * Supports non-ascii characters +	//  * Does not allow parentheses and single quotes +	//  * Cuts off commas, exclamation points, and periods off as last character + +	// @todo this causes problems with <attr = "val"> +	// must be in <attr="val"> format (no space). +	// By default htmlawed rewrites tags to this format. +	// if PHP supported conditional negative lookbehinds we could use this: +	// $r = preg_replace_callback('/(?<!=)(?<![ ])?(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', +	$r = preg_replace_callback('/(?<![=\/"\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\']+)/i', +	create_function( +		'$matches', +		' +			$url = $matches[1]; +			$punc = ""; +			$last = substr($url, -1, 1); +			if (in_array($last, array(".", "!", ",", "(", ")"))) { +				$punc = $last; +				$url = rtrim($url, ".!,()"); +			} +			$urltext = str_replace("/", "/<wbr />", $url); +			return "<a href=\"$url\" rel=\"nofollow\">$urltext</a>$punc"; +		' +	), $text); + +	return $r; +} + +/** + * Create paragraphs from text with line spacing + * + * @param string $pee The string + * @deprecated Use elgg_autop instead + * @todo Add deprecation warning in 1.9 + * + * @return string + **/ +function autop($pee) { +	return elgg_autop($pee); +} + +/** + * Create paragraphs from text with line spacing + * + * @param string $string The string + * + * @return string + **/ +function elgg_autop($string) { +	return ElggAutoP::getInstance()->process($string); +} + +/** + * Returns an excerpt. + * Will return up to n chars stopping at the nearest space. + * If no spaces are found (like in Japanese) will crop off at the + * n char mark. Adds ... if any text was chopped. + * + * @param string $text      The full text to excerpt + * @param int    $num_chars Return a string up to $num_chars long + * + * @return string + * @since 1.7.2 + */ +function elgg_get_excerpt($text, $num_chars = 250) { +	$text = trim(elgg_strip_tags($text)); +	$string_length = elgg_strlen($text); + +	if ($string_length <= $num_chars) { +		return $text; +	} + +	// handle cases +	$excerpt = elgg_substr($text, 0, $num_chars); +	$space = elgg_strrpos($excerpt, ' ', 0); + +	// don't crop if can't find a space. +	if ($space === FALSE) { +		$space = $num_chars; +	} +	$excerpt = trim(elgg_substr($excerpt, 0, $space)); + +	if ($string_length != elgg_strlen($excerpt)) { +		$excerpt .= '...'; +	} + +	return $excerpt; +} + +/** + * Handles formatting of ampersands in urls + * + * @param string $url The URL + * + * @return string + * @since 1.7.1 + */ +function elgg_format_url($url) { +	return preg_replace('/&(?!amp;)/', '&', $url); +} + +/** + * Converts an associative array into a string of well-formed attributes + * + * @note usually for HTML, but could be useful for XML too... + * + * @param array $attrs An associative array of attr => val pairs + * + * @return string HTML attributes to be inserted into a tag (e.g., <tag $attrs>) + */ +function elgg_format_attributes(array $attrs) { +	$attrs = elgg_clean_vars($attrs); +	$attributes = array(); + +	if (isset($attrs['js'])) { +		//@todo deprecated notice? + +		if (!empty($attrs['js'])) { +			$attributes[] = $attrs['js']; +		} + +		unset($attrs['js']); +	} + +	foreach ($attrs as $attr => $val) { +		$attr = strtolower($attr); + +		if ($val === TRUE) { +			$val = $attr; //e.g. checked => TRUE ==> checked="checked" +		} + +		// ignore $vars['entity'] => ElggEntity stuff +		if ($val !== NULL && $val !== false && (is_array($val) || !is_object($val))) { + +			// allow $vars['class'] => array('one', 'two'); +			// @todo what about $vars['style']? Needs to be semi-colon separated... +			if (is_array($val)) { +				$val = implode(' ', $val); +			} + +			$val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8', false); +			$attributes[] = "$attr=\"$val\""; +		} +	} + +	return implode(' ', $attributes); +} + +/** + * Preps an associative array for use in {@link elgg_format_attributes()}. + * + * Removes all the junk that {@link elgg_view()} puts into $vars. + * Maintains backward compatibility with attributes like 'internalname' and 'internalid' + * + * @note This function is called automatically by elgg_format_attributes(). No need to + *       call it yourself before using elgg_format_attributes(). + * + * @param array $vars The raw $vars array with all it's dirtiness (config, url, etc.) + * + * @return array The array, ready to be used in elgg_format_attributes(). + * @access private + */ +function elgg_clean_vars(array $vars = array()) { +	unset($vars['config']); +	unset($vars['url']); +	unset($vars['user']); + +	// backwards compatibility code +	if (isset($vars['internalname'])) { +		$vars['name'] = $vars['internalname']; +		unset($vars['internalname']); +	} + +	if (isset($vars['internalid'])) { +		$vars['id'] = $vars['internalid']; +		unset($vars['internalid']); +	} + +	if (isset($vars['__ignoreInternalid'])) { +		unset($vars['__ignoreInternalid']); +	} + +	if (isset($vars['__ignoreInternalname'])) { +		unset($vars['__ignoreInternalname']); +	} + +	return $vars; +} + +/** + * Converts shorthand urls to absolute urls. + * + * If the url is already absolute or protocol-relative, no change is made. + * + * @example + * elgg_normalize_url('');                   // 'http://my.site.com/' + * elgg_normalize_url('dashboard');          // 'http://my.site.com/dashboard' + * elgg_normalize_url('http://google.com/'); // no change + * elgg_normalize_url('//google.com/');      // no change + * + * @param string $url The URL to normalize + * + * @return string The absolute url + */ +function elgg_normalize_url($url) { +	// see https://bugs.php.net/bug.php?id=51192 +	// from the bookmarks save action. +	$php_5_2_13_and_below = version_compare(PHP_VERSION, '5.2.14', '<'); +	$php_5_3_0_to_5_3_2 = version_compare(PHP_VERSION, '5.3.0', '>=') && +			version_compare(PHP_VERSION, '5.3.3', '<'); + +	if ($php_5_2_13_and_below || $php_5_3_0_to_5_3_2) { +		$tmp_address = str_replace("-", "", $url); +		$validated = filter_var($tmp_address, FILTER_VALIDATE_URL); +	} else { +		$validated = filter_var($url, FILTER_VALIDATE_URL); +	} + +	// work around for handling absoluate IRIs (RFC 3987) - see #4190 +	if (!$validated && (strpos($url, 'http:') === 0) || (strpos($url, 'https:') === 0)) { +		$validated = true; +	} + +	if ($validated) { +		// all normal URLs including mailto: +		return $url; + +	} elseif (preg_match("#^(\#|\?|//)#i", $url)) { +		// '//example.com' (Shortcut for protocol.) +		// '?query=test', #target +		return $url; +	 +	} elseif (stripos($url, 'javascript:') === 0 || stripos($url, 'mailto:') === 0) { +		// 'javascript:' and 'mailto:' +		// Not covered in FILTER_VALIDATE_URL +		return $url; + +	} elseif (preg_match("#^[^/]*\.php(\?.*)?$#i", $url)) { +		// 'install.php', 'install.php?step=step' +		return elgg_get_site_url() . $url; + +	} elseif (preg_match("#^[^/]*\.#i", $url)) { +		// 'example.com', 'example.com/subpage' +		return "http://$url"; + +	} else { +		// 'page/handler', 'mod/plugin/file.php' + +		// trim off any leading / because the site URL is stored +		// with a trailing / +		return elgg_get_site_url() . ltrim($url, '/'); +	} +} + +/** + * When given a title, returns a version suitable for inclusion in a URL + * + * @param string $title The title + * + * @return string The optimised title + * @since 1.7.2 + */ +function elgg_get_friendly_title($title) { + +	// return a URL friendly title to short circuit normal title formatting +	$params = array('title' => $title); +	$result = elgg_trigger_plugin_hook('format', 'friendly:title', $params, NULL); +	if ($result) { +		return $result; +	} + +	// titles are often stored HTML encoded +	$title = html_entity_decode($title, ENT_QUOTES, 'UTF-8'); +	 +	$title = ElggTranslit::urlize($title); + +	return $title; +} + +/** + * Formats a UNIX timestamp in a friendly way (eg "less than a minute ago") + * + * @see elgg_view_friendly_time() + * + * @param int $time A UNIX epoch timestamp + * + * @return string The friendly time string + * @since 1.7.2 + */ +function elgg_get_friendly_time($time) { + +	// return a time string to short circuit normal time formatting +	$params = array('time' => $time); +	$result = elgg_trigger_plugin_hook('format', 'friendly:time', $params, NULL); +	if ($result) { +		return $result; +	} + +	$diff = time() - (int)$time; + +	$minute = 60; +	$hour = $minute * 60; +	$day = $hour * 24; + +	if ($diff < $minute) { +			return elgg_echo("friendlytime:justnow"); +	} else if ($diff < $hour) { +		$diff = round($diff / $minute); +		if ($diff == 0) { +			$diff = 1; +		} + +		if ($diff > 1) { +			return elgg_echo("friendlytime:minutes", array($diff)); +		} else { +			return elgg_echo("friendlytime:minutes:singular", array($diff)); +		} +	} else if ($diff < $day) { +		$diff = round($diff / $hour); +		if ($diff == 0) { +			$diff = 1; +		} + +		if ($diff > 1) { +			return elgg_echo("friendlytime:hours", array($diff)); +		} else { +			return elgg_echo("friendlytime:hours:singular", array($diff)); +		} +	} else { +		$diff = round($diff / $day); +		if ($diff == 0) { +			$diff = 1; +		} + +		if ($diff > 1) { +			return elgg_echo("friendlytime:days", array($diff)); +		} else { +			return elgg_echo("friendlytime:days:singular", array($diff)); +		} +	} +} + +/** + * Strip tags and offer plugins the chance. + * Plugins register for output:strip_tags plugin hook. + * Original string included in $params['original_string'] + * + * @param string $string Formatted string + * + * @return string String run through strip_tags() and any plugin hooks. + */ +function elgg_strip_tags($string) { +	$params['original_string'] = $string; + +	$string = strip_tags($string); +	$string = elgg_trigger_plugin_hook('format', 'strip_tags', $params, $string); + +	return $string; +} + +/** + * Apply html_entity_decode() to a string while re-entitising HTML + * special char entities to prevent them from being decoded back to their + * unsafe original forms. + * + * This relies on html_entity_decode() not translating entities when + * doing so leaves behind another entity, e.g. &gt; if decoded would + * create > which is another entity itself. This seems to escape the + * usual behaviour where any two paired entities creating a HTML tag are + * usually decoded, i.e. a lone > is not decoded, but <foo> would + * be decoded to <foo> since it creates a full tag. + * + * Note: This function is poorly explained in the manual - which is really + * bad given its potential for misuse on user input already escaped elsewhere. + * Stackoverflow is littered with advice to use this function in the precise + * way that would lead to user input being capable of injecting arbitrary HTML. + * + * @param string $string + * + * @return string + * + * @author Pádraic Brady + * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com) + * @license Released under dual-license GPL2/MIT by explicit permission of Pádraic Brady + * + * @access private + */ +function _elgg_html_decode($string) { +	$string = str_replace( +		array('>', '<', '&', '"', '''), +		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), +		$string +	); +	$string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8'); +	$string = str_replace( +		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), +		array('>', '<', '&', '"', '''), +		$string +	); +	return $string; +} + +/** + * Prepares query string for output to prevent CSRF attacks. + *  + * @param string $string + * @return string + * + * @access private + */ +function _elgg_get_display_query($string) { +	//encode <,>,&, quotes and characters above 127 +	if (function_exists('mb_convert_encoding')) {
 +		$display_query = mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
 +	} else {
 +		// if no mbstring extension, we just strip characters
 +		$display_query = preg_replace("/[^\x01-\x7F]/", "", $string);
 +	}
 +	return htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false); +} + +/** + * Unit tests for Output + * + * @param string  $hook   unit_test + * @param string $type   system + * @param mixed  $value  Array of tests + * @param mixed  $params Params + * + * @return array + * @access private + */ +function output_unit_test($hook, $type, $value, $params) { +	global $CONFIG; +	$value[] = $CONFIG->path . 'engine/tests/api/output.php'; +	return $value; +} + +/** + * Initialise the Output subsystem. + * + * @return void + * @access private + */ +function output_init() { +	elgg_register_plugin_hook_handler('unit_test', 'system', 'output_unit_test'); +} + +elgg_register_event_handler('init', 'system', 'output_init'); diff --git a/engine/lib/pagehandler.php b/engine/lib/pagehandler.php 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..4aaffc160 --- /dev/null +++ b/engine/lib/pageowner.php @@ -0,0 +1,297 @@ +<?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); + +	if ($guid) { +		$page_owner_guid = $guid; +	} + +	return $guid; +} + +/** + * Gets the owner entity for the current page. + * + * @note Access is disabled when getting the page owner entity. + * + * @return ElggUser|ElggGroup|false The current page owner or false if none. + * + * @since 1.8.0 + */ +function elgg_get_page_owner_entity() { +	$guid = elgg_get_page_owner_guid(); +	if ($guid > 0) { +		$ia = elgg_set_ignore_access(true); +		$owner = get_entity($guid); +		elgg_set_ignore_access($ia); + +		return $owner; +	} + +	return false; +} + +/** + * Set the guid of the entity that owns this page + * + * @param int $guid The guid of the page owner + * @return void + * @since 1.8.0 + */ +function elgg_set_page_owner_guid($guid) { +	elgg_get_page_owner_guid($guid); +} + +/** + * 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)) { +			elgg_set_ignore_access($ia); +			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(urldecode($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..d5d3db466 --- /dev/null +++ b/engine/lib/plugins.php @@ -0,0 +1,1179 @@ +<?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() { +	// @todo $site unused, can remove? +	$site = get_config('site'); + +	$dir = elgg_get_plugins_path(); +	$db_prefix = elgg_get_config('dbprefix'); + +	$options = array( +		'type' => 'object', +		'subtype' => 'plugin', +		'selects' => array('plugin_oe.*'), +		'joins' => array("JOIN {$db_prefix}objects_entity plugin_oe on plugin_oe.guid = e.guid"), +		'limit' => ELGG_ENTITIES_NO_VALUE +	); + +	$old_ia = elgg_set_ignore_access(true); +	$old_access = access_get_show_hidden_status(); +	access_show_hidden_entities(true); +	$known_plugins = elgg_get_entities_from_relationship($options); +	/* @var ElggPlugin[] $known_plugins */ + +	if (!$known_plugins) { +		$known_plugins = array(); +	} + +	// 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->isEnabled()) { +				$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 ElggPlugin|false + * @since 1.8.0 + */ +function elgg_get_plugin_from_id($plugin_id) { +	$map = (array) elgg_get_config('plugins_by_id_map'); +	if (isset($map[$plugin_id])) { +		return $map[$plugin_id]; +	} + +	$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; +	} else { +		$max = 1; +	} + +	// 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() { +	$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 ElggPlugin[] + * @since 1.8.0 + * @access private + */ +function elgg_get_plugins($status = 'active', $site_guid = null) { +	$db_prefix = get_config('dbprefix'); +	$priority = elgg_namespace_plugin_private_setting('internal', 'priority'); + +	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); + +	$missing_plugins = array(); +	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') { +	$provided = elgg_get_plugins_provides($type, $name); +	if (!$provided) { +		return array( +			'status' => false, +			'version' => '' +		); +	} + +	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 string $name      Name of the setting + * @param int    $user_guid Defaults to logged in user + * @param string $plugin_id Defaults to contextual plugin name + * + * @return bool + * @since 1.8.0 + */ +function elgg_unset_plugin_user_setting($name, $user_guid = null, $plugin_id = null) { +	if ($plugin_id) { +		$plugin = elgg_get_plugin_from_id($plugin_id); +	} else { +		$plugin = elgg_get_calling_plugin_entity(); +	} + +	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 string  $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; +} + +/** + * Checks on deactivate plugin event if disabling it won't create unmet dependencies and blocks disable in such case. + * + * @param string $event  deactivate + * @param string $type   plugin + * @param array  $params Parameters array containing entry with ELggPlugin instance under 'plugin_entity' key + * @return bool  false to block plugin deactivation action + * + * @access private + */ +function _plugins_deactivate_dependency_check($event, $type, $params) { +	$plugin_id = $params['plugin_entity']->getManifest()->getPluginID(); +	$plugin_name = $params['plugin_entity']->getManifest()->getName(); + +	$active_plugins = elgg_get_plugins(); + +	$dependents = array(); +	foreach ($active_plugins as $plugin) { +		$manifest = $plugin->getManifest(); +		$requires = $manifest->getRequires(); + +		foreach ($requires as $required) { +			if ($required['type'] == 'plugin' && $required['name'] == $plugin_id) { +				// there are active dependents +				$dependents[$manifest->getPluginID()] = $plugin; +			} +		} +	} + +	if ($dependents) { +		$list = '<ul>'; +		// construct error message and prevent disabling +		foreach ($dependents as $dependent) { +			$list .= '<li>' . $dependent->getManifest()->getName() . '</li>'; +		} +		$list .= '</ul>'; + +		register_error(elgg_echo('ElggPlugin:Dependencies:ActiveDependent', array($plugin_name, $list))); + +		return false; +	} +} + +/** + * 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'); +	 +	// note - plugins are booted by the time this handler is registered +	// deactivation due to error may have already occurred +	elgg_register_event_handler('deactivate', 'plugin', '_plugins_deactivate_dependency_check'); + +	elgg_register_action("plugins/settings/save", '', 'admin'); +	elgg_register_action("plugins/usersettings/save"); + +	elgg_register_action('admin/plugins/activate', '', 'admin'); +	elgg_register_action('admin/plugins/deactivate', '', 'admin'); +	elgg_register_action('admin/plugins/activate_all', '', 'admin'); +	elgg_register_action('admin/plugins/deactivate_all', '', 'admin'); + +	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..b0cd627fc --- /dev/null +++ b/engine/lib/relationships.php @@ -0,0 +1,643 @@ +<?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 ElggRelationship|stdClass + * @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 ElggRelationship|false + */ +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 ElggRelationship|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 = row_to_elggrelationship(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 ElggRelationship[] + */ +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 ElggEntity[]|mixed If count, int. If not count, array. false on errors. + * @since 1.7.0 + */ +function elgg_get_entities_from_relationship($options) { +	$defaults = array( +		'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 bool $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 Options array for retrieval of entities + * + * @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 ElggEntity[]|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 string + * @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 ElggEntity[] + * @access private + */ +function get_attachments($guid, $type = "") { +	$options = array( +					'relationship' => 'attached', +					'relationship_guid' => $guid, +					'inverse_relationship' => false, +					'type' => $type, +					'subtypes' => '', +					'owner_guid' => 0, +					'order_by' => 'time_created desc', +					'limit' => 10, +					'offset' => 0, +					'count' => false, +					'site_guid' => 0 +				); +	$attached = elgg_get_entities_from_relationship($options); +	return $attached; +} + +/** + * Function to remove a particular attachment between two objects + * + * @param int $guid_one This is the target object + * @param int $guid_two This is the object to remove from $guid_one + * + * @return 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 + * @throws InvalidParameterException + * @access private + */ +function export_relationship_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']; + +	$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) { +	/* @var ElggRelationship $object */ +	$user_one = get_entity($object->guid_one); +	/* @var ElggUser $user_one */ + +	return notify_user($object->guid_two, +			$object->guid_one, +			elgg_echo('friend:newfriend:subject', array($user_one->name)), +			elgg_echo("friend:newfriend:body", array($user_one->name, $user_one->getURL())) +	); +} + +// Register the import hook +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..e92040eb7 --- /dev/null +++ b/engine/lib/river.php @@ -0,0 +1,703 @@ +<?php +/** + * Elgg river. + * Activity stream functions. + * + * @package Elgg.Core + * @subpackage SocialModel.River + */ + +/** + * Adds an item to the river. + * + * @param string $view          The view that will handle the river item (must exist) + * @param string $action_type   An arbitrary string to define the action (eg 'comment', 'create') + * @param int    $subject_guid  The GUID of the entity doing the action + * @param int    $object_guid   The GUID of the entity being acted upon + * @param int    $access_id     The access ID of the river item (default: same as the object) + * @param int    $posted        The UNIX epoch timestamp of the river item (default: now) + * @param int    $annotation_id The annotation ID associated with this river entry + * + * @return int/bool River ID or false on failure + */ +function add_to_river($view, $action_type, $subject_guid, $object_guid, $access_id = "", +$posted = 0, $annotation_id = 0) { + +	global $CONFIG; + +	// use default viewtype for when called from web services api +	if (!elgg_view_exists($view, 'default')) { +		return false; +	} +	if (!($subject = get_entity($subject_guid))) { +		return false; +	} +	if (!($object = get_entity($object_guid))) { +		return false; +	} +	if (empty($action_type)) { +		return false; +	} +	if ($posted == 0) { +		$posted = time(); +	} +	if ($access_id === "") { +		$access_id = $object->access_id; +	} +	$type = $object->getType(); +	$subtype = $object->getSubtype(); + +	$view = sanitise_string($view); +	$action_type = sanitise_string($action_type); +	$subject_guid = sanitise_int($subject_guid); +	$object_guid = sanitise_int($object_guid); +	$access_id = sanitise_int($access_id); +	$posted = sanitise_int($posted); +	$annotation_id = sanitise_int($annotation_id); + +	$values = array( +		'type' => $type, +		'subtype' => $subtype, +		'action_type' => $action_type, +		'access_id' => $access_id, +		'view' => $view, +		'subject_guid' => $subject_guid, +		'object_guid' => $object_guid, +		'annotation_id' => $annotation_id, +		'posted' => $posted, +	); + +	// return false to stop insert +	$values = elgg_trigger_plugin_hook('creating', 'river', null, $values); +	if ($values == false) { +		// inserting did not fail - it was just prevented +		return true; +	} + +	extract($values); + +	// Attempt to save river item; return success status +	$id = insert_data("insert into {$CONFIG->dbprefix}river " . +		" set type = '$type', " . +		" subtype = '$subtype', " . +		" action_type = '$action_type', " . +		" access_id = $access_id, " . +		" view = '$view', " . +		" subject_guid = $subject_guid, " . +		" object_guid = $object_guid, " . +		" annotation_id = $annotation_id, " . +		" posted = $posted"); + +	// update the entities which had the action carried out on it +	// @todo shouldn't this be down elsewhere? Like when an annotation is saved? +	if ($id) { +		update_entity_last_action($object_guid, $posted); +		 +		$river_items = elgg_get_river(array('id' => $id)); +		if ($river_items) { +			elgg_trigger_event('created', 'river', $river_items[0]); +		} +		return $id; +	} else { +		return false; +	} +} + +/** + * Delete river items + * + * @warning not checking access (should we?) + * + * @param array $options Parameters: + *   ids                  => INT|ARR River item id(s) + *   subject_guids        => INT|ARR Subject guid(s) + *   object_guids         => INT|ARR Object guid(s) + *   annotation_ids       => INT|ARR The identifier of the annotation(s) + *   action_types         => STR|ARR The river action type(s) identifier + *   views                => STR|ARR River view(s) + * + *   types                => STR|ARR Entity type string(s) + *   subtypes             => STR|ARR Entity subtype string(s) + *   type_subtype_pairs   => ARR     Array of type => subtype pairs where subtype + *                                   can be an array of subtype strings + * + *   posted_time_lower    => INT     The lower bound on the time posted + *   posted_time_upper    => INT     The upper bound on the time posted + * + * @return bool + * @since 1.8.0 + */ +function elgg_delete_river(array $options = array()) { +	global $CONFIG; + +	$defaults = array( +		'ids'                  => ELGG_ENTITIES_ANY_VALUE, + +		'subject_guids'	       => ELGG_ENTITIES_ANY_VALUE, +		'object_guids'         => ELGG_ENTITIES_ANY_VALUE, +		'annotation_ids'       => ELGG_ENTITIES_ANY_VALUE, + +		'views'                => ELGG_ENTITIES_ANY_VALUE, +		'action_types'         => ELGG_ENTITIES_ANY_VALUE, + +		'types'	               => ELGG_ENTITIES_ANY_VALUE, +		'subtypes'             => ELGG_ENTITIES_ANY_VALUE, +		'type_subtype_pairs'   => ELGG_ENTITIES_ANY_VALUE, + +		'posted_time_lower'	   => ELGG_ENTITIES_ANY_VALUE, +		'posted_time_upper'	   => ELGG_ENTITIES_ANY_VALUE, + +		'wheres'               => array(), +		'joins'                => array(), + +	); + +	$options = array_merge($defaults, $options); + +	$singulars = array('id', 'subject_guid', 'object_guid', 'annotation_id', 'action_type', 'view', 'type', 'subtype'); +	$options = elgg_normalise_plural_options_array($options, $singulars); + +	$wheres = $options['wheres']; + +	$wheres[] = elgg_get_guid_based_where_sql('rv.id', $options['ids']); +	$wheres[] = elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']); +	$wheres[] = elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']); +	$wheres[] = elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']); +	$wheres[] = elgg_river_get_action_where_sql($options['action_types']); +	$wheres[] = elgg_river_get_view_where_sql($options['views']); +	$wheres[] = elgg_get_river_type_subtype_where_sql('rv', $options['types'], +		$options['subtypes'], $options['type_subtype_pairs']); + +	if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) { +		$wheres[] = "rv.posted >= {$options['posted_time_lower']}"; +	} + +	if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) { +		$wheres[] = "rv.posted <= {$options['posted_time_upper']}"; +	} + +	// see if any functions failed +	// remove empty strings on successful functions +	foreach ($wheres as $i => $where) { +		if ($where === FALSE) { +			return FALSE; +		} elseif (empty($where)) { +			unset($wheres[$i]); +		} +	} + +	// remove identical where clauses +	$wheres = array_unique($wheres); + +	$query = "DELETE rv.* FROM {$CONFIG->dbprefix}river rv "; + +	// remove identical join clauses +	$joins = array_unique($options['joins']); +	 +	// add joins +	foreach ($joins as $j) { +		$query .= " $j "; +	} + +	// add wheres +	$query .= ' WHERE '; + +	foreach ($wheres as $w) { +		$query .= " $w AND "; +	} +	$query .= "1=1"; + +	return delete_data($query); +} + +/** + * Get river items + * + * @note If using types and subtypes in a query, they are joined with an AND. + * + * @param array $options Parameters: + *   ids                  => INT|ARR River item id(s) + *   subject_guids        => INT|ARR Subject guid(s) + *   object_guids         => INT|ARR Object guid(s) + *   annotation_ids       => INT|ARR The identifier of the annotation(s) + *   action_types         => STR|ARR The river action type(s) identifier + *   posted_time_lower    => INT     The lower bound on the time posted + *   posted_time_upper    => INT     The upper bound on the time posted + * + *   types                => STR|ARR Entity type string(s) + *   subtypes             => STR|ARR Entity subtype string(s) + *   type_subtype_pairs   => ARR     Array of type => subtype pairs where subtype + *                                   can be an array of subtype strings + * + *   relationship         => STR     Relationship identifier + *   relationship_guid    => INT|ARR Entity guid(s) + *   inverse_relationship => BOOL    Subject or object of the relationship (false) + * + * 	 limit                => INT     Number to show per page (20) + *   offset               => INT     Offset in list (0) + *   count                => BOOL    Count the river items? (false) + *   order_by             => STR     Order by clause (rv.posted desc) + *   group_by             => STR     Group by clause + * + * @return array|int + * @since 1.8.0 + */ +function elgg_get_river(array $options = array()) { +	global $CONFIG; + +	$defaults = array( +		'ids'                  => ELGG_ENTITIES_ANY_VALUE, + +		'subject_guids'	       => ELGG_ENTITIES_ANY_VALUE, +		'object_guids'         => ELGG_ENTITIES_ANY_VALUE, +		'annotation_ids'       => ELGG_ENTITIES_ANY_VALUE, +		'action_types'         => ELGG_ENTITIES_ANY_VALUE, + +		'relationship'         => NULL, +		'relationship_guid'    => NULL, +		'inverse_relationship' => FALSE, + +		'types'	               => ELGG_ENTITIES_ANY_VALUE, +		'subtypes'             => ELGG_ENTITIES_ANY_VALUE, +		'type_subtype_pairs'   => ELGG_ENTITIES_ANY_VALUE, + +		'posted_time_lower'	   => ELGG_ENTITIES_ANY_VALUE, +		'posted_time_upper'	   => ELGG_ENTITIES_ANY_VALUE, + +		'limit'                => 20, +		'offset'               => 0, +		'count'                => FALSE, + +		'order_by'             => 'rv.posted desc', +		'group_by'             => ELGG_ENTITIES_ANY_VALUE, + +		'wheres'               => array(), +		'joins'                => array(), +	); + +	$options = array_merge($defaults, $options); + +	$singulars = array('id', 'subject_guid', 'object_guid', 'annotation_id', 'action_type', 'type', 'subtype'); +	$options = elgg_normalise_plural_options_array($options, $singulars); + +	$wheres = $options['wheres']; + +	$wheres[] = elgg_get_guid_based_where_sql('rv.id', $options['ids']); +	$wheres[] = elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']); +	$wheres[] = elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']); +	$wheres[] = elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']); +	$wheres[] = elgg_river_get_action_where_sql($options['action_types']); +	$wheres[] = elgg_get_river_type_subtype_where_sql('rv', $options['types'], +		$options['subtypes'], $options['type_subtype_pairs']); + +	if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) { +		$wheres[] = "rv.posted >= {$options['posted_time_lower']}"; +	} + +	if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) { +		$wheres[] = "rv.posted <= {$options['posted_time_upper']}"; +	} + +	$joins = $options['joins']; + +	if ($options['relationship_guid']) { +		$clauses = elgg_get_entity_relationship_where_sql( +				'rv.subject_guid', +				$options['relationship'], +				$options['relationship_guid'], +				$options['inverse_relationship']); +		if ($clauses) { +			$wheres = array_merge($wheres, $clauses['wheres']); +			$joins = array_merge($joins, $clauses['joins']); +		} +	} + +	// see if any functions failed +	// remove empty strings on successful functions +	foreach ($wheres as $i => $where) { +		if ($where === FALSE) { +			return FALSE; +		} elseif (empty($where)) { +			unset($wheres[$i]); +		} +	} + +	// remove identical where clauses +	$wheres = array_unique($wheres); + +	if (!$options['count']) { +		$query = "SELECT DISTINCT rv.* FROM {$CONFIG->dbprefix}river rv "; +	} else { +		$query = "SELECT count(DISTINCT rv.id) as total FROM {$CONFIG->dbprefix}river rv "; +	} + +	// add joins +	foreach ($joins as $j) { +		$query .= " $j "; +	} + +	// add wheres +	$query .= ' WHERE '; + +	foreach ($wheres as $w) { +		$query .= " $w AND "; +	} + +	$query .= elgg_river_get_access_sql(); + +	if (!$options['count']) { +		$options['group_by'] = sanitise_string($options['group_by']); +		if ($options['group_by']) { +			$query .= " GROUP BY {$options['group_by']}"; +		} + +		$options['order_by'] = sanitise_string($options['order_by']); +		$query .= " ORDER BY {$options['order_by']}"; + +		if ($options['limit']) { +			$limit = sanitise_int($options['limit']); +			$offset = sanitise_int($options['offset'], false); +			$query .= " LIMIT $offset, $limit"; +		} + +		$river_items = get_data($query, 'elgg_row_to_elgg_river_item'); +		_elgg_prefetch_river_entities($river_items); + +		return $river_items; +	} else { +		$total = get_data_row($query); +		return (int)$total->total; +	} +} + +/** + * Prefetch entities that will be displayed in the river. + * + * @param ElggRiverItem[] $river_items + * @access private + */ +function _elgg_prefetch_river_entities(array $river_items) { +	// prefetch objects and subjects +	$guids = array(); +	foreach ($river_items as $item) { +		if ($item->subject_guid && !_elgg_retrieve_cached_entity($item->subject_guid)) { +			$guids[$item->subject_guid] = true; +		} +		if ($item->object_guid && !_elgg_retrieve_cached_entity($item->object_guid)) { +			$guids[$item->object_guid] = true; +		} +	} +	if ($guids) { +		// avoid creating oversized query +		// @todo how to better handle this? +		$guids = array_slice($guids, 0, 300, true); +		// return value unneeded, just priming cache +		elgg_get_entities(array( +			'guids' => array_keys($guids), +			'limit' => 0, +		)); +	} + +	// prefetch object containers +	$guids = array(); +	foreach ($river_items as $item) { +		$object = $item->getObjectEntity(); +		if ($object->container_guid && !_elgg_retrieve_cached_entity($object->container_guid)) { +			$guids[$object->container_guid] = true; +		} +	} +	if ($guids) { +		$guids = array_slice($guids, 0, 300, true); +		elgg_get_entities(array( +			'guids' => array_keys($guids), +			'limit' => 0, +		)); +	} +} + +/** + * List river items + * + * @param array $options Any options from elgg_get_river() plus: + * 	 pagination => BOOL Display pagination links (true) + * + * @return string + * @since 1.8.0 + */ +function elgg_list_river(array $options = array()) { +	global $autofeed; +	$autofeed = true; + +	$defaults = array( +		'offset'     => (int) max(get_input('offset', 0), 0), +		'limit'      => (int) max(get_input('limit', 20), 0), +		'pagination' => TRUE, +		'list_class' => 'elgg-list-river elgg-river', // @todo remove elgg-river in Elgg 1.9 +	); +	 +	$options = array_merge($defaults, $options); +	 +	if (!$options["limit"] && !$options["offset"]) {
 +		// no need for pagination if listing is unlimited
 +		$options["pagination"] = false;
 +	} + +	$options['count'] = TRUE; +	$count = elgg_get_river($options); + +	$options['count'] = FALSE; +	$items = elgg_get_river($options); + +	$options['count'] = $count; +	$options['items'] = $items; +	 +	return elgg_view('page/components/list', $options); +} + +/** + * Convert a database row to a new ElggRiverItem + * + * @param stdClass $row Database row from the river table + * + * @return ElggRiverItem + * @since 1.8.0 + * @access private + */ +function elgg_row_to_elgg_river_item($row) { +	if (!($row instanceof stdClass)) { +		return NULL; +	} + +	return new ElggRiverItem($row); +} + +/** + * Get the river's access where clause + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_river_get_access_sql() { +	// rewrite default access where clause to work with river table +	return str_replace("and enabled='yes'", '', +		str_replace('owner_guid', 'rv.subject_guid', +		str_replace('access_id', 'rv.access_id', get_access_sql_suffix()))); +} + +/** + * Returns SQL where clause for type and subtype on river table + * + * @internal This is a simplified version of elgg_get_entity_type_subtype_where_sql() + * which could be used for all queries once the subtypes have been denormalized. + * + * @param string     $table    'rv' + * @param NULL|array $types    Array of types or NULL if none. + * @param NULL|array $subtypes Array of subtypes or NULL if none + * @param NULL|array $pairs    Array of pairs of types and subtypes + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_get_river_type_subtype_where_sql($table, $types, $subtypes, $pairs) { +	// short circuit if nothing is requested +	if (!$types && !$subtypes && !$pairs) { +		return ''; +	} + +	$wheres = array(); +	$types_wheres = array(); +	$subtypes_wheres = array(); + +	// if no pairs, use types and subtypes +	if (!is_array($pairs)) { +		if ($types) { +			if (!is_array($types)) { +				$types = array($types); +			} +			foreach ($types as $type) { +				$type = sanitise_string($type); +				$types_wheres[] = "({$table}.type = '$type')"; +			} +		} + +		if ($subtypes) { +			if (!is_array($subtypes)) { +				$subtypes = array($subtypes); +			} +			foreach ($subtypes as $subtype) { +				$subtype = sanitise_string($subtype); +				$subtypes_wheres[] = "({$table}.subtype = '$subtype')"; +			} +		} + +		if (is_array($types_wheres) && count($types_wheres)) { +			$types_wheres = array(implode(' OR ', $types_wheres)); +		} + +		if (is_array($subtypes_wheres) && count($subtypes_wheres)) { +			$subtypes_wheres = array('(' . implode(' OR ', $subtypes_wheres) . ')'); +		} + +		$wheres = array(implode(' AND ', array_merge($types_wheres, $subtypes_wheres))); + +	} else { +		// using type/subtype pairs +		foreach ($pairs as $paired_type => $paired_subtypes) { +			$paired_type = sanitise_string($paired_type); +			if (is_array($paired_subtypes)) { +				$paired_subtypes = array_map('sanitise_string', $paired_subtypes); +				$paired_subtype_str = implode("','", $paired_subtypes); +				if ($paired_subtype_str) { +					$wheres[] = "({$table}.type = '$paired_type'" +						. " AND {$table}.subtype IN ('$paired_subtype_str'))"; +				} +			} else { +				$paired_subtype = sanitise_string($paired_subtypes); +				$wheres[] = "({$table}.type = '$paired_type'" +					. " AND {$table}.subtype = '$paired_subtype')"; +			} +		} +	} + +	if (is_array($wheres) && count($wheres)) { +		$where = implode(' OR ', $wheres); +		return "($where)"; +	} + +	return ''; +} + +/** + * Get the where clause based on river action type strings + * + * @param array $types Array of action type strings + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_river_get_action_where_sql($types) { +	if (!$types) { +		return ''; +	} + +	if (!is_array($types)) { +		$types = sanitise_string($types); +		return "(rv.action_type = '$types')"; +	} + +	// sanitize types array +	$types_sanitized = array(); +	foreach ($types as $type) { +		$types_sanitized[] = sanitise_string($type); +	} + +	$type_str = implode("','", $types_sanitized); +	return "(rv.action_type IN ('$type_str'))"; +} + +/** + * Get the where clause based on river view strings + * + * @param array $views Array of view strings + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_river_get_view_where_sql($views) { +	if (!$views) { +		return ''; +	} + +	if (!is_array($views)) { +		$views = sanitise_string($views); +		return "(rv.view = '$views')"; +	} + +	// sanitize views array +	$views_sanitized = array(); +	foreach ($views as $view) { +		$views_sanitized[] = sanitise_string($view); +	} + +	$view_str = implode("','", $views_sanitized); +	return "(rv.view IN ('$view_str'))"; +} + +/** + * Sets the access ID on river items for a particular object + * + * @param int $object_guid The GUID of the entity + * @param int $access_id   The access ID + * + * @return bool Depending on success + */ +function update_river_access_by_object($object_guid, $access_id) { +	// Sanitise +	$object_guid = (int) $object_guid; +	$access_id = (int) $access_id; + +	// Load config +	global $CONFIG; + +	// Remove +	$query = "update {$CONFIG->dbprefix}river +		set access_id = {$access_id} +		where object_guid = {$object_guid}"; +	return update_data($query); +} + +/** + * Page handler for activity + * + * @param array $page + * @return bool + * @access private + */ +function elgg_river_page_handler($page) { +	global $CONFIG; + +	elgg_set_page_owner_guid(elgg_get_logged_in_user_guid()); + +	// make a URL segment available in page handler script +	$page_type = elgg_extract(0, $page, 'all'); +	$page_type = preg_replace('[\W]', '', $page_type); +	if ($page_type == 'owner') { +		$page_type = 'mine'; +	} +	set_input('page_type', $page_type); + +	require_once("{$CONFIG->path}pages/river.php"); +	return true; +} + +/** + * Register river unit tests + * @access private + */ +function elgg_river_test($hook, $type, $value) { +	global $CONFIG; +	$value[] = $CONFIG->path . 'engine/tests/api/river.php'; +	return $value; +} + +/** + * Initialize river library + * @access private + */ +function elgg_river_init() { +	elgg_register_page_handler('activity', 'elgg_river_page_handler'); +	$item = new ElggMenuItem('activity', elgg_echo('activity'), 'activity'); +	elgg_register_menu_item('site', $item); +	 +	elgg_register_widget_type('river_widget', elgg_echo('river:widget:title'), elgg_echo('river:widget:description')); + +	elgg_register_action('river/delete', '', 'admin'); + +	elgg_register_plugin_hook_handler('unit_test', 'system', 'elgg_river_test'); +} + +elgg_register_event_handler('init', 'system', 'elgg_river_init'); diff --git a/engine/lib/sessions.php b/engine/lib/sessions.php new file mode 100644 index 000000000..e3d5ce9cd --- /dev/null +++ b/engine/lib/sessions.php @@ -0,0 +1,656 @@ +<?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; + +	$user_guid = (int)$user_guid; + +	// cannot use magic metadata here because of recursion + +	// must support the old way of getting admin from metadata +	// in order to run the upgrade to move it into the users table. +	$version = (int) datalist_get('version'); + +	if ($version < 2010040201) { +		$admin = get_metastring_id('admin'); +		$yes = get_metastring_id('yes'); +		$one = get_metastring_id('1'); + +		$query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as e, +			{$CONFIG->dbprefix}metadata as md +			WHERE ( +				md.name_id = '$admin' +				AND md.value_id IN ('$yes', '$one') +				AND e.guid = md.entity_guid +				AND e.guid = {$user_guid} +				AND e.banned = 'no' +			)"; +	} else { +		$query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as e +			WHERE ( +				e.guid = {$user_guid} +				AND e.admin = 'yes' +			)"; +	} + +	// normalizing the results from get_data() +	// See #1242 +	$info = get_data($query); +	if (!((is_array($info) && count($info) < 1) || $info === FALSE)) { +		return TRUE; +	} +	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) { +	// 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 + +	// if memcache is enabled, invalidate the user in memcache @see https://github.com/Elgg/Elgg/issues/3143 +	if (is_memcache_available()) { +		// this needs to happen with a shutdown function because of the timing with set_last_login() +		register_shutdown_function("_elgg_invalidate_memcache_for_entity", $_SESSION['guid']); +	} +	 +	return true; +} + +/** + * Log the current user out + * + * @return bool + */ +function logout() { +	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); +	} +} + +/** + * 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..3de0eccc2 --- /dev/null +++ b/engine/lib/sites.php @@ -0,0 +1,256 @@ +<?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) { +	$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) { +	$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, +		'type' => 'object', +		'subtype' => $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 string $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..4cb0bb0b8 --- /dev/null +++ b/engine/lib/statistics.php @@ -0,0 +1,126 @@ +<?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() { +	$limit = max(0, (int) get_input("limit", 10)); +	$offset = max(0, (int) get_input("offset", 0)); +	 +	$count = find_active_users(600, $limit, $offset, true); +	$objects = find_active_users(600, $limit, $offset); + +	if ($objects) { +		return elgg_view_entity_list($objects, array( +			'count' => $count, +			'limit' => $limit, +			'offset' => $offset +		)); +	} +	return ''; +} + +/** + * 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..84302632e --- /dev/null +++ b/engine/lib/system_log.php @@ -0,0 +1,311 @@ +<?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. + * + * @todo too many args, and the first arg is too confusing + * + * @param int|array $by_user    The guid(s) of the user(s) who initiated the event. + *                              Use 0 for unowned entries. Anything else falsey means anyone. + * @param string    $event      The event you are searching on. + * @param string    $class      The class of object it effects. + * @param string    $type       The type + * @param string    $subtype    The subtype. + * @param int       $limit      Maximum number of responses to return. + * @param int       $offset     Offset of where to start. + * @param bool      $count      Return count or not + * @param int       $timebefore Lower time limit + * @param int       $timeafter  Upper time limit + * @param int       $object_id  GUID of an object + * @param string    $ip_address The IP address. + * @return mixed + */ +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 = "") { + +	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) { + +		/* @var ElggEntity|ElggExtender $object */ +		if (datalist_get('version') < 2012012000) { +			// this is a site that doesn't have the ip_address column yet +			return; +		} + +		// reset cache if it has grown too large +		if (!is_array($log_cache) || $cache_size > 500) { +			$log_cache = array(); +			$cache_size = 0; +		} + +		// Has loggable interface, extract the necessary information and store +		$object_id = (int)$object->getSystemLogID(); +		$object_class = $object->getClassName(); +		$object_type = $object->getType(); +		$object_subtype = $object->getSubtype(); +		$event = sanitise_string($event); +		$time = time(); + +		if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { +			$ip_address = array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])); +		} elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) { +			$ip_address = array_pop(explode(',', $_SERVER['HTTP_X_REAL_IP'])); +		} else { +			$ip_address = $_SERVER['REMOTE_ADDR']; +		} +		$ip_address = sanitise_string($ip_address); + +		$performed_by = elgg_get_logged_in_user_guid(); + +		if (isset($object->access_id)) { +			$access_id = $object->access_id; +		} 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..586a9b9e4 --- /dev/null +++ b/engine/lib/tags.php @@ -0,0 +1,354 @@ +<?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 array An associated array of tags with a weighting, this can then be mapped to a display class. + * @access private + */ +function generate_tag_cloud(array $tags, $buckets = 6) { +	$cloud = array(); + +	$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 	object[]|false 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 != ''"; + +	$sanitised_tags = array(); +	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..158ec9ec1 --- /dev/null +++ b/engine/lib/upgrade.php @@ -0,0 +1,365 @@ +<?php +/** + * Elgg upgrade library. + * Contains code for handling versioning and upgrades. + * + * @package Elgg.Core + * @subpackage Upgrade + */ + +/** + * Run any php upgrade scripts which are required + * + * @param int  $version Version upgrading from. + * @param bool $quiet   Suppress errors.  Don't use this. + * + * @return bool + * @access private + */ +function upgrade_code($version, $quiet = FALSE) { +	// do not remove - upgrade scripts depend on this +	global $CONFIG; +	 +	$version = (int) $version; +	$upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/'; +	$processed_upgrades = elgg_get_processed_upgrades(); + +	// upgrading from 1.7 to 1.8. Need to bootstrap. +	if (!$processed_upgrades) { +		elgg_upgrade_bootstrap_17_to_18(); + +		// grab accurate processed upgrades +		$processed_upgrades = elgg_get_processed_upgrades(); +	} + +	$upgrade_files = elgg_get_upgrade_files($upgrade_path); + +	if ($upgrade_files === false) { +		return false; +	} + +	$upgrades = elgg_get_unprocessed_upgrades($upgrade_files, $processed_upgrades); + +	// Sort and execute +	sort($upgrades); + +	foreach ($upgrades as $upgrade) { +		$upgrade_version = elgg_get_upgrade_file_version($upgrade); +		$success = true; + +		// hide all errors. +		if ($quiet) { +			// hide include errors as well as any exceptions that might happen +			try { +				if (!@include("$upgrade_path/$upgrade")) { +					$success = false; +					error_log("Could not include $upgrade_path/$upgrade"); +				} +			} catch (Exception $e) { +				$success = false; +				error_log($e->getmessage()); +			} +		} else { +			if (!include("$upgrade_path/$upgrade")) { +				$success = false; +				error_log("Could not include $upgrade_path/$upgrade"); +			} +		} + +		if ($success) { +			// incrementally set upgrade so we know where to start if something fails. +			$processed_upgrades[] = $upgrade; + +			// don't set the version to a lower number in instances where an upgrade +			// has been merged from a lower version of Elgg +			if ($upgrade_version > $version) { +				datalist_set('version', $upgrade_version); +			} + +			elgg_set_processed_upgrades($processed_upgrades); +		} else { +			return false; +		} +	} + +	return true; +} + +/** + * Saves the processed upgrades to a dataset. + * + * @param array $processed_upgrades An array of processed upgrade filenames + *                                  (not the path, just the file) + * @return bool + * @access private + */ +function elgg_set_processed_upgrades(array $processed_upgrades) { +	$processed_upgrades = array_unique($processed_upgrades); +	return datalist_set('processed_upgrades', serialize($processed_upgrades)); +} + +/** + * Gets a list of processes upgrades + * + * @return mixed Array of processed upgrade filenames or false + * @access private + */ +function elgg_get_processed_upgrades() { +	$upgrades = datalist_get('processed_upgrades'); +	$unserialized = unserialize($upgrades); +	return $unserialized; +} + +/** + * Returns the version of the upgrade filename. + * + * @param string $filename The upgrade filename. No full path. + * @return int|false + * @since 1.8.0 + * @access private + */ +function elgg_get_upgrade_file_version($filename) { +	preg_match('/^([0-9]{10})([\.a-z0-9-_]+)?\.(php)$/i', $filename, $matches); + +	if (isset($matches[1])) { +		return (int) $matches[1]; +	} + +	return false; +} + +/** + * Returns a list of upgrade files relative to the $upgrade_path dir. + * + * @param string $upgrade_path The up + * @return array|false + * @access private + */ +function elgg_get_upgrade_files($upgrade_path = null) { +	if (!$upgrade_path) { +		$upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/'; +	} +	$upgrade_path = sanitise_filepath($upgrade_path); +	$handle = opendir($upgrade_path); + +	if (!$handle) { +		return false; +	} + +	$upgrade_files = array(); + +	while ($upgrade_file = readdir($handle)) { +		// make sure this is a wellformed upgrade. +		if (is_dir($upgrade_path . '$upgrade_file')) { +			continue; +		} +		$upgrade_version = elgg_get_upgrade_file_version($upgrade_file); +		if (!$upgrade_version) { +			continue; +		} +		$upgrade_files[] = $upgrade_file; +	} + +	sort($upgrade_files); + +	return $upgrade_files; +} + +/** + * Get the current Elgg version information + * + * @param bool $humanreadable Whether to return a human readable version (default: false) + * + * @return string|false Depending on success + */ +function get_version($humanreadable = false) { +	global $CONFIG; + +	static $version, $release; + +	if (isset($CONFIG->path)) { +		if (!isset($version) || !isset($release)) { +			if (!include($CONFIG->path . "version.php")) { +				return false; +			} +		} +		return (!$humanreadable) ? $version : $release; +	} + +	return false; +} + +/** + * Checks if any upgrades need to be run. + * + * @param null|array $upgrade_files      Optional upgrade files + * @param null|array $processed_upgrades Optional processed upgrades + * + * @return array + * @access private + */ +function elgg_get_unprocessed_upgrades($upgrade_files = null, $processed_upgrades = null) { +	if ($upgrade_files === null) { +		$upgrade_files = elgg_get_upgrade_files(); +	} + +	if ($processed_upgrades === null) { +		$processed_upgrades = unserialize(datalist_get('processed_upgrades')); +		if (!is_array($processed_upgrades)) { +			$processed_upgrades = array(); +		} +	} + +	$unprocessed = array_diff($upgrade_files, $processed_upgrades); +	return $unprocessed; +} + +/** + * Determines whether or not the database needs to be upgraded. + * + * @return bool Depending on whether or not the db version matches the code version + * @access private + */ +function version_upgrade_check() { +	$dbversion = (int) datalist_get('version'); +	$version = get_version(); + +	if ($version > $dbversion) { +		return TRUE; +	} + +	return FALSE; +} + +/** + * Upgrades Elgg Database and code + * + * @return bool + * @access private + */ +function version_upgrade() { +	// It's possible large upgrades could exceed the max execution time. +	set_time_limit(0); + +	$dbversion = (int) datalist_get('version'); + +	// No version number? Oh snap...this is an upgrade from a clean installation < 1.7. +	// Run all upgrades without error reporting and hope for the best. +	// See https://github.com/elgg/elgg/issues/1432 for more. +	$quiet = !$dbversion; + +	// Note: Database upgrades are deprecated as of 1.8.  Use code upgrades.  See #1433 +	if (db_upgrade($dbversion, '', $quiet)) { +		system_message(elgg_echo('upgrade:db')); +	} + +	if (upgrade_code($dbversion, $quiet)) { +		system_message(elgg_echo('upgrade:core')); + +		// Now we trigger an event to give the option for plugins to do something +		$upgrade_details = new stdClass; +		$upgrade_details->from = $dbversion; +		$upgrade_details->to = get_version(); + +		elgg_trigger_event('upgrade', 'upgrade', $upgrade_details); + +		return true; +	} + +	return false; +} + +/** + * Boot straps into 1.8 upgrade system from 1.7 + * + * This runs all the 1.7 upgrades, then sets the processed_upgrades to all existing 1.7 upgrades. + * Control is then passed back to the main upgrade function which detects and runs the + * 1.8 upgrades, regardless of filename convention. + * + * @return bool + * @access private + */ +function elgg_upgrade_bootstrap_17_to_18() { +	$db_version = (int) datalist_get('version'); + +	// the 1.8 upgrades before the upgrade system change that are interspersed with 1.7 upgrades. +	$upgrades_18 = array( +		'2010111501.php', +		'2010121601.php', +		'2010121602.php', +		'2010121701.php', +		'2010123101.php', +		'2011010101.php', +	); + +	$upgrade_files = elgg_get_upgrade_files(); +	$processed_upgrades = array(); + +	foreach ($upgrade_files as $upgrade_file) { +		// ignore if not in 1.7 format or if it's a 1.8 upgrade +		if (in_array($upgrade_file, $upgrades_18) || !preg_match("/[0-9]{10}\.php/", $upgrade_file)) { +			continue; +		} + +		$upgrade_version = elgg_get_upgrade_file_version($upgrade_file); + +		// this has already been run in a previous 1.7.X -> 1.7.X upgrade +		if ($upgrade_version < $db_version) { +			$processed_upgrades[] = $upgrade_file; +		} +	} + +	return elgg_set_processed_upgrades($processed_upgrades); +} + +/** + * Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades. + * + * @see _elgg_upgrade_lock() + * + * @return bool + * @access private + */ +function _elgg_upgrade_lock() { +	global $CONFIG; +	 +	if (!_elgg_upgrade_is_locked()) { +		// lock it +		insert_data("create table {$CONFIG->dbprefix}upgrade_lock (id INT)"); +		elgg_log('Locked for upgrade.', 'NOTICE'); +		return true; +	} +	 +	elgg_log('Cannot lock for upgrade: already locked.', 'WARNING'); +	return false; +} + +/** + * Unlocks upgrade. + * + * @see _elgg_upgrade_lock() + * + * @access private + */ +function _elgg_upgrade_unlock() { +	global $CONFIG; +	delete_data("drop table {$CONFIG->dbprefix}upgrade_lock"); +	elgg_log('Upgrade unlocked.', 'NOTICE'); +} + +/** + * Checks if upgrade is locked + * + * @return bool + * @access private + */ +function _elgg_upgrade_is_locked() { +	global $CONFIG; + +	$is_locked = count(get_data("show tables like '{$CONFIG->dbprefix}upgrade_lock'")); + +	// @todo why? +	_elgg_invalidate_query_cache(); + +	return $is_locked; +} diff --git a/engine/lib/upgrades/2008100701.php b/engine/lib/upgrades/2008100701.php 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..3ad113fb2 --- /dev/null +++ b/engine/lib/upgrades/2009102801.php @@ -0,0 +1,222 @@ +<?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 $ENTITY_CACHE, $CONFIG; +/** + * Upgrade file locations + */ +$users = mysql_query("SELECT guid, username +	FROM {$CONFIG->dbprefix}users_entity WHERE username != ''"); +while ($user = mysql_fetch_object($users)) { +	$ENTITY_CACHE = array(); +	_elgg_invalidate_query_cache(); + +	$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..4779295fd --- /dev/null +++ b/engine/lib/upgrades/2010033101.php @@ -0,0 +1,70 @@ +<?php + +/** + * Conditional upgrade for UTF8 as described in https://github.com/elgg/elgg/issues/1928 + */ + +// get_version() returns the code version. +// we want the DB version. +$dbversion = (int) datalist_get('version'); + +// 2009100701 was the utf8 upgrade for 1.7. +// if we've already upgraded, don't try again. +if ($dbversion < 2009100701) { +	// if the default client connection is utf8 there is no reason +	// to run this upgrade because the strings are already stored correctly. + +	// start a new link to the DB to see what its defaults are. +	$link = mysql_connect($CONFIG->dbhost, $CONFIG->dbuser, $CONFIG->dbpass, TRUE); +	mysql_select_db($CONFIG->dbname, $link); + +	$q = "SHOW VARIABLES LIKE 'character_set_client'"; +	$r = mysql_query($q); +	$client = mysql_fetch_assoc($r); + +	$q = "SHOW VARIABLES LIKE 'character_set_connection'"; +	$r = mysql_query($q); +	$connection = mysql_fetch_assoc($r); + +	// only run upgrade if not already talking utf8. +	if ($client['Value'] != 'utf8' && $connection['Value'] != 'utf8') { +		$qs = array(); +		$qs[] = "SET NAMES utf8"; + +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}metastrings DISABLE KEYS"; +		$qs[] = "REPLACE INTO {$CONFIG->dbprefix}metastrings (id, string) +			SELECT id, unhex(hex(convert(string using latin1))) +			FROM {$CONFIG->dbprefix}metastrings"; +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}metastrings ENABLE KEYS"; + +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}groups_entity DISABLE KEYS"; +		$qs[] = "REPLACE INTO {$CONFIG->dbprefix}groups_entity (guid, name, description) +			SELECT guid, unhex(hex(convert(name using latin1))), +				unhex(hex(convert(description using latin1))) +			FROM {$CONFIG->dbprefix}groups_entity"; +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}groups_entity ENABLE KEYS"; + +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}objects_entity DISABLE KEYS"; +		$qs[] = "REPLACE INTO {$CONFIG->dbprefix}objects_entity (guid, title, description) +			SELECT guid, unhex(hex(convert(title using latin1))), +				unhex(hex(convert(description using latin1))) +			FROM {$CONFIG->dbprefix}objects_entity"; +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}objects_entity ENABLE KEYS"; + +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity DISABLE KEYS"; +		$qs[] = "REPLACE INTO {$CONFIG->dbprefix}users_entity +			(guid, name, username, password, salt, email, language, code, +			banned, last_action, prev_last_action, last_login, prev_last_login) +				SELECT guid, unhex(hex(convert(name using latin1))), +					username, password, salt, email, language, code, +					banned, last_action, prev_last_action, last_login, prev_last_login +				FROM {$CONFIG->dbprefix}users_entity"; +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity ENABLE KEYS"; + +		foreach ($qs as $q) { +			if (!update_data($q)) { +				throw new Exception('Couldn\'t execute upgrade query: ' . $q); +			} +		} +	} +} diff --git a/engine/lib/upgrades/2010040201.php b/engine/lib/upgrades/2010040201.php new file mode 100644 index 000000000..789bf5dfc --- /dev/null +++ b/engine/lib/upgrades/2010040201.php @@ -0,0 +1,41 @@ +<?php + +/** + * Pull admin metadata setting into users_entity table column + */ + +$siteadmin = get_metastring_id('siteadmin'); +$admin = get_metastring_id('admin'); +$yes = get_metastring_id('yes'); +$one = get_metastring_id('1'); + +$qs = array(); + +$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity DISABLE KEYS"; + +$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity +	ADD admin ENUM('yes', 'no') NOT NULL DEFAULT 'no' AFTER `banned`"; + +$qs[] = "UPDATE {$CONFIG->dbprefix}users_entity SET admin = 'yes' where guid IN (select x.guid FROM( +SELECT * FROM {$CONFIG->dbprefix}users_entity as e, +	{$CONFIG->dbprefix}metadata as md +	WHERE ( +		md.name_id IN ('$admin', '$siteadmin') +		AND md.value_id IN ('$yes', '$one') +		AND e.guid = md.entity_guid +		AND e.banned = 'no' +	)) as x)"; + +$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity ADD KEY admin (admin)"; + +$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity ENABLE KEYS"; + +$qs[] = "DELETE FROM {$CONFIG->dbprefix}metadata +	WHERE ( +		name_id IN ('$admin', '$siteadmin') +		AND value_id IN ('$yes', '$one') +	)"; + +foreach ($qs as $q) { +	update_data($q); +} diff --git a/engine/lib/upgrades/2010052601.php b/engine/lib/upgrades/2010052601.php new file mode 100644 index 000000000..a9cca6dc5 --- /dev/null +++ b/engine/lib/upgrades/2010052601.php @@ -0,0 +1,27 @@ +<?php + +// Upgrade to fix encoding issues on group data: #1963 + +elgg_set_ignore_access(TRUE); + +$params = array('type' => 'group', +				'limit' => 0); +$groups = elgg_get_entities($params); +if ($groups) { +	foreach ($groups as $group) { +		$group->name = _elgg_html_decode($group->name); +		$group->description = _elgg_html_decode($group->description); +		$group->briefdescription = _elgg_html_decode($group->briefdescription); +		$group->website = _elgg_html_decode($group->website); +		if ($group->interests) { +			$tags = $group->interests; +			foreach ($tags as $index => $tag) { +				$tags[$index] = _elgg_html_decode($tag); +			} +			$group->interests = $tags; +		} + +		$group->save(); +	} +} +elgg_set_ignore_access(FALSE); diff --git a/engine/lib/upgrades/2010060101.php b/engine/lib/upgrades/2010060101.php new file mode 100644 index 000000000..bb7f7c1a6 --- /dev/null +++ b/engine/lib/upgrades/2010060101.php @@ -0,0 +1,16 @@ +<?php + +/** + * Clears old simplecache variables out of database + */ + +$query = "DELETE FROM {$CONFIG->dbprefix}datalists WHERE name LIKE 'simplecache%'"; + +delete_data($query); + +if ($CONFIG->simplecache_enabled) { +	datalist_set('simplecache_enabled', 1); +	elgg_regenerate_simplecache(); +} else { +	datalist_set('simplecache_enabled', 0); +} diff --git a/engine/lib/upgrades/2010060401.php b/engine/lib/upgrades/2010060401.php new file mode 100644 index 000000000..6d628b8eb --- /dev/null +++ b/engine/lib/upgrades/2010060401.php @@ -0,0 +1,59 @@ +<?php + +/** + * Get each user's notify* relationships and confirm that they have a friend + * or member relationship depending on type. This fixes the notify relationships + * that were not updated to due to #1837 + */ + +$count = 0; + +$user_guids = mysql_query("SELECT guid FROM {$CONFIG->dbprefix}users_entity"); +while ($user = mysql_fetch_object($user_guids)) { + +	$query = "SELECT * FROM {$CONFIG->dbprefix}entity_relationships +		WHERE guid_one=$user->guid AND relationship LIKE 'notify%'"; +	$relationships = mysql_query($query); +	if (mysql_num_rows($relationships) == 0) { +		// no notify relationships for this user +		continue; +	} + +	while ($obj = mysql_fetch_object($relationships)) { +		$query = "SELECT type FROM {$CONFIG->dbprefix}entities WHERE guid=$obj->guid_two"; +		$results = mysql_query($query); +		if (mysql_num_rows($results) == 0) { +			// entity doesn't exist - shouldn't be possible +			continue; +		} + +		$entity = mysql_fetch_object($results); + +		switch ($entity->type) { +			case 'user': +				$relationship_type = 'friend'; +				break; +			case 'group': +				$relationship_type = 'member'; +				break; +		} + +		if (isset($relationship_type)) { +				$query = "SELECT * FROM {$CONFIG->dbprefix}entity_relationships +							WHERE guid_one=$user->guid AND relationship='$relationship_type' +							AND guid_two=$obj->guid_two"; +				$results = mysql_query($query); + +			if (mysql_num_rows($results) == 0) { +				$query = "DELETE FROM {$CONFIG->dbprefix}entity_relationships WHERE id=$obj->id"; +				mysql_query($query); +				$count++; +			} +		} +	} + +} + +if (is_callable('error_log')) { +	error_log("Deleted $count notify relationships in upgrade"); +} diff --git a/engine/lib/upgrades/2010061501.php b/engine/lib/upgrades/2010061501.php new file mode 100644 index 000000000..744c28fd5 --- /dev/null +++ b/engine/lib/upgrades/2010061501.php @@ -0,0 +1,75 @@ +<?php +/** + * utf8 database conversion and file merging for usernames with multibyte chars + * + */ + + +// check that we need to do the utf8 conversion +// C&P logic from 2010033101 +$dbversion = (int) datalist_get('version'); + +if ($dbversion < 2009100701) { +	// start a new link to the DB to see what its defaults are. +	$link = mysql_connect($CONFIG->dbhost, $CONFIG->dbuser, $CONFIG->dbpass, TRUE); +	mysql_select_db($CONFIG->dbname, $link); + +	$q = "SHOW VARIABLES LIKE 'character_set_client'"; +	$r = mysql_query($q); +	$client = mysql_fetch_assoc($r); + +	$q = "SHOW VARIABLES LIKE 'character_set_connection'"; +	$r = mysql_query($q); +	$connection = mysql_fetch_assoc($r); + +	// only run upgrade if not already talking utf8 +	if ($client['Value'] != 'utf8' && $connection['Value'] != 'utf8') { +		$qs = array(); +		$qs[] = "SET NAMES utf8"; + +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity DISABLE KEYS"; +		$qs[] = "REPLACE INTO {$CONFIG->dbprefix}users_entity +			(guid, name, username, password, salt, email, language, code, +			banned, admin, last_action, prev_last_action, last_login, prev_last_login) + +			SELECT guid, name, unhex(hex(convert(username using latin1))), +				password, salt, email, language, code, +				banned, admin, last_action, prev_last_action, last_login, prev_last_login +			FROM {$CONFIG->dbprefix}users_entity"; + +		$qs[] = "ALTER TABLE {$CONFIG->dbprefix}users_entity ENABLE KEYS"; + +		foreach ($qs as $q) { +			if (!update_data($q)) { +				throw new Exception('Couldn\'t execute upgrade query: ' . $q); +			} +		} + +		global $ENTITY_CACHE; + +		/** +			Upgrade file locations +		 */ +		// new connection to force into utf8 mode to get the old name +		$link = mysql_connect($CONFIG->dbhost, $CONFIG->dbuser, $CONFIG->dbpass, TRUE); +		mysql_select_db($CONFIG->dbname, $link); + +		// must be the first command +		mysql_query("SET NAMES utf8"); + +		$users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity +			WHERE username != ''", $link); +		while ($user = mysql_fetch_object($users)) { +			$ENTITY_CACHE = array(); +			_elgg_invalidate_query_cache(); + + +			$to = $CONFIG->dataroot . user_file_matrix($user->guid); +			foreach (array('1_0', '1_1', '1_6') as $version) { +				$function = "file_matrix_$version"; +				$from = $CONFIG->dataroot . $function($user->username); +				merge_directories($from, $to, $move = TRUE, $preference = 'from'); +			} +		} +	} +} diff --git a/engine/lib/upgrades/2010062301.php b/engine/lib/upgrades/2010062301.php new file mode 100644 index 000000000..f679fa46d --- /dev/null +++ b/engine/lib/upgrades/2010062301.php @@ -0,0 +1,33 @@ +<?php + +/** + * Change ownership of group ACLs to group entity + */ + +elgg_set_ignore_access(TRUE); + +$params = array('type' => 'group', +				'limit' => 0); +$groups = elgg_get_entities($params); +if ($groups) { +	foreach ($groups as $group) { +		$acl = $group->group_acl; + +		try { +			$query = "UPDATE {$CONFIG->dbprefix}access_collections +				SET owner_guid = $group->guid WHERE id = $acl"; +			update_data($query); +		} catch (Exception $e) { +			// no acl so create one +			$ac_name = elgg_echo('groups:group') . ": " . $group->name; +			$group_acl = create_access_collection($ac_name, $group->guid); +			if ($group_acl) { +				create_metadata($group->guid, 'group_acl', $group_acl, 'integer', $group->owner_guid); +				$object->group_acl = $group_id; +			} +		} + +	} +} +elgg_set_ignore_access(FALSE); + diff --git a/engine/lib/upgrades/2010062302.php b/engine/lib/upgrades/2010062302.php new file mode 100644 index 000000000..fe33e12ea --- /dev/null +++ b/engine/lib/upgrades/2010062302.php @@ -0,0 +1,33 @@ +<?php + +/** + * Make sure that everyone who belongs to a group is a member of the group's access collection + */ + + +elgg_set_ignore_access(TRUE); + +$params = array('type' => 'group', 'limit' => 0); +$groups = elgg_get_entities($params); +if ($groups) { +	foreach ($groups as $group) { +		$acl = $group->group_acl; + +		$query = "SELECT u.guid FROM {$CONFIG->dbprefix}users_entity u +			JOIN {$CONFIG->dbprefix}entity_relationships r +				ON u.guid = r.guid_one AND r.relationship = 'member' AND r.guid_two = $group->guid +			LEFT JOIN {$CONFIG->dbprefix}access_collection_membership a +				ON u.guid = a.user_guid AND a.access_collection_id = $acl +				WHERE a.user_guid IS NULL"; + +		$results = get_data($query); +		if ($results != FALSE) { +			foreach ($results as $user) { +				$insert = "INSERT INTO {$CONFIG->dbprefix}access_collection_membership +							(user_guid, access_collection_id) VALUES ($user->guid, $acl)"; +				insert_data($insert); +			} +		} +	} +} +elgg_set_ignore_access(FALSE); diff --git a/engine/lib/upgrades/2010070301.php b/engine/lib/upgrades/2010070301.php new file mode 100644 index 000000000..af5c80419 --- /dev/null +++ b/engine/lib/upgrades/2010070301.php @@ -0,0 +1,9 @@ +<?php + +/** + * Group join river view has been renamed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river SET view='river/relationship/member/create' +			WHERE view='river/group/create' AND action_type='join'"; +update_data($query); diff --git a/engine/lib/upgrades/2010071001.php b/engine/lib/upgrades/2010071001.php new file mode 100644 index 000000000..5594493a8 --- /dev/null +++ b/engine/lib/upgrades/2010071001.php @@ -0,0 +1,58 @@ +<?php +/** + *	Change profile image names to use guid rather than username + */ + +/** + * Need the same function to generate a user matrix, but can't call it + * the same thing as the previous update. + * + * @param int $guid User guid. + * + * @return string File matrix + */ +function user_file_matrix_2010071001($guid) { +	// lookup the entity +	$user = get_entity($guid); +	if ($user->type != 'user') { +		// only to be used for user directories +		return FALSE; +	} + +	if (!$user->time_created) { +		// no idea where this user has its files +		return FALSE; +	} + +	$time_created = date('Y/m/d', $user->time_created); +	return "$time_created/$user->guid/"; +} + +$sizes = array('large', 'medium', 'small', 'tiny', 'master', 'topbar'); + +global $ENTITY_CACHE, $CONFIG; +$users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity +	WHERE username != ''"); +while ($user = mysql_fetch_object($users)) { +	$ENTITY_CACHE = array(); +	_elgg_invalidate_query_cache(); + +	$user_directory = user_file_matrix_2010071001($user->guid); +	if (!$user_directory) { +		continue; +	} +	$profile_directory = $CONFIG->dataroot . $user_directory . "profile/"; +	if (!file_exists($profile_directory)) { +		continue; +	} + +	foreach ($sizes as $size) { +		$old_filename = "$profile_directory{$user->username}{$size}.jpg"; +		$new_filename = "$profile_directory{$user->guid}{$size}.jpg"; +		if (file_exists($old_filename)) { +			if (!rename($old_filename, $new_filename)) { +				error_log("Failed to rename profile photo for $user->username"); +			} +		} +	} +} diff --git a/engine/lib/upgrades/2010071002.php b/engine/lib/upgrades/2010071002.php new file mode 100644 index 000000000..52aa15ef5 --- /dev/null +++ b/engine/lib/upgrades/2010071002.php @@ -0,0 +1,50 @@ +<?php +/** + * Update the notifications based on all friends and access collections + */ + +// loop through all users checking collections and notifications +global $ENTITY_CACHE, $CONFIG; +global $NOTIFICATION_HANDLERS; +$users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity +	WHERE username != ''"); +while ($user = mysql_fetch_object($users)) { +	$ENTITY_CACHE = array(); +	_elgg_invalidate_query_cache(); + +	$user = get_entity($user->guid); +	foreach ($NOTIFICATION_HANDLERS as $method => $foo) { +		$notify = "notify$method"; +		$metaname = "collections_notifications_preferences_$method"; +		$collections_preferences = $user->$metaname; +		if (!$collections_preferences) { +			continue; +		} +		if (!is_array($collections_preferences)) { +			$collections_preferences = array($collections_preferences); +		} +		foreach ($collections_preferences as $collection_id) { +			// check the all friends notifications +			if ($collection_id == -1) { +				$options = array( +					'relationship' => 'friend', +					'relationship_guid' => $user->guid, +					'limit' => 0 +				); +				$friends = elgg_get_entities_from_relationship($options); +				foreach ($friends as $friend) { +					if (!check_entity_relationship($user->guid, $notify, $friend->guid)) { +						add_entity_relationship($user->guid, $notify, $friend->guid); +					} +				} +			} else { +				$members = get_members_of_access_collection($collection_id, TRUE); +				foreach ($members as $member) { +					if (!check_entity_relationship($user->guid, $notify, $members)) { +						add_entity_relationship($user->guid, $notify, $member); +					} +				} +			} +		} +	} +} diff --git a/engine/lib/upgrades/2010111501.php b/engine/lib/upgrades/2010111501.php new file mode 100644 index 000000000..15e4a7d35 --- /dev/null +++ b/engine/lib/upgrades/2010111501.php @@ -0,0 +1,33 @@ +<?php +/** + * Set validation metadata on unvalidated users to false rather than  + * not existing. This is needed because of the change in how validation is + * being handled. + */ + +// turn off system log because of all the metadata this can create +elgg_unregister_event_handler('all', 'all', 'system_log_listener'); +elgg_unregister_event_handler('log', 'systemlog', 'system_log_default_logger'); + +$ia = elgg_set_ignore_access(TRUE); +$hidden_entities = access_get_show_hidden_status(); +access_show_hidden_entities(TRUE); + +$validated_id = get_metastring_id('validated'); +$one_id = get_metastring_id(1); + +$query = "SELECT guid FROM {$CONFIG->dbprefix}entities e +			WHERE e.type = 'user' AND e.enabled = 'no' AND +			NOT EXISTS ( +				SELECT 1 FROM {$CONFIG->dbprefix}metadata md +				WHERE md.entity_guid = e.guid +				AND md.name_id = $validated_id +				AND md.value_id = $one_id)"; + +$user_guids = mysql_query($query); +while ($user_guid = mysql_fetch_object($user_guids)) { +	create_metadata($user_guid->guid, 'validated', false, '', 0, ACCESS_PUBLIC, false); +} + +access_show_hidden_entities($hidden_entities); +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2010121601.php b/engine/lib/upgrades/2010121601.php new file mode 100644 index 000000000..ad7d26adb --- /dev/null +++ b/engine/lib/upgrades/2010121601.php @@ -0,0 +1,9 @@ +<?php +/** + * Create friends river view has been changed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river  +			SET view='river/relationship/friend/create', action_type='create' +			WHERE view='friends/river/create' AND action_type='friend'"; +update_data($query); diff --git a/engine/lib/upgrades/2010121602.php b/engine/lib/upgrades/2010121602.php new file mode 100644 index 000000000..5b0996b5e --- /dev/null +++ b/engine/lib/upgrades/2010121602.php @@ -0,0 +1,10 @@ +<?php +/** + * Create comment river view has been changed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river +			SET view='river/annotation/generic_comment/create' +			WHERE view='annotation/annotate' AND action_type='comment'"; +update_data($query); + diff --git a/engine/lib/upgrades/2010121701.php b/engine/lib/upgrades/2010121701.php new file mode 100644 index 000000000..375654bac --- /dev/null +++ b/engine/lib/upgrades/2010121701.php @@ -0,0 +1,10 @@ +<?php +/** + * Create group forum topic river view has been changed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river +			SET view='river/object/groupforumtopic/create' +			WHERE view='river/forum/topic/create' AND action_type='create'"; +update_data($query); + diff --git a/engine/lib/upgrades/2010123101.php b/engine/lib/upgrades/2010123101.php new file mode 100644 index 000000000..f4befd1a8 --- /dev/null +++ b/engine/lib/upgrades/2010123101.php @@ -0,0 +1,9 @@ +<?php +/** + * Set default access for older sites + */ + +$access = elgg_get_config('default_access'); +if ($access == false) { +	elgg_save_config('default_access', ACCESS_LOGGED_IN); +} diff --git a/engine/lib/upgrades/2011010101.php b/engine/lib/upgrades/2011010101.php new file mode 100644 index 000000000..f4411ee20 --- /dev/null +++ b/engine/lib/upgrades/2011010101.php @@ -0,0 +1,98 @@ +<?php +/** + * Migrate plugins to the new system using ElggPlugin and private settings + */ + +$old_ia = elgg_set_ignore_access(true); + +$site = get_config('site'); +$old_plugin_order = unserialize($site->pluginorder); +$old_enabled_plugins = $site->enabled_plugins; + +$db_prefix = get_config('dbprefix'); +$plugin_subtype_id = get_subtype_id('object', 'plugin'); + +// easy one first: make sure the the site owns all plugin entities. +$q = "UPDATE {$db_prefix}entities e +	SET owner_guid = $site->guid, container_guid = $site->guid +	WHERE e.type = 'object' AND e.subtype = $plugin_subtype_id"; + +$r = update_data($q); + +// rewrite all plugin:setting:* to ELGG_PLUGIN_USER_SETTING_PREFIX . * +$q = "UPDATE {$db_prefix}private_settings +	SET name = replace(name, 'plugin:settings:', '" . ELGG_PLUGIN_USER_SETTING_PREFIX . "') +	WHERE name LIKE 'plugin:settings:%'"; + +$r = update_data($q); + +// grab current plugin GUIDs to add a temp priority +$q = "SELECT * FROM {$db_prefix}entities e +	JOIN {$db_prefix}objects_entity oe ON e.guid = oe.guid +	WHERE e.type = 'object' AND e.subtype = $plugin_subtype_id"; + +$plugins = get_data($q); + +foreach ($plugins as $plugin) { +	$priority = elgg_namespace_plugin_private_setting('internal', 'priority'); +	set_private_setting($plugin->guid, $priority, 0); +} + +// force regenerating plugin entities +elgg_generate_plugin_entities(); + +// set the priorities for all plugins +// this function rewrites it to a normal index so use the current one. +elgg_set_plugin_priorities($old_plugin_order); + +// add relationships for enabled plugins +if ($old_enabled_plugins) { +	// they might only have one plugin enabled. +	if (!is_array($old_enabled_plugins)) { +		$old_enabled_plugins = array($old_enabled_plugins); +	} + +	// sometimes there were problems and you'd get 1000s of enabled plugins. +	$old_enabled_plugins = array_unique($old_enabled_plugins); + +	foreach ($old_enabled_plugins as $plugin_id) { +		$plugin = elgg_get_plugin_from_id($plugin_id); + +		if ($plugin) { +			$plugin->activate(); +		} +	} +} + +// invalidate caches +elgg_invalidate_simplecache(); +elgg_reset_system_cache(); + +// clean up. +remove_metadata($site->guid, 'pluginorder'); +remove_metadata($site->guid, 'enabled_plugins'); + +elgg_set_ignore_access($old_id); + +/** + * @hack + * + * We stop the upgrade at this point because plugins weren't given the chance to + * load due to the new plugin code introduced with Elgg 1.8. Instead, we manually + * set the version and start the upgrade process again. + * + * The variables from upgrade_code() are available because this script was included + */ +if ($upgrade_version > $version) { +	datalist_set('version', $upgrade_version); +} + +// add ourselves to the processed_upgrades. +$processed_upgrades[] = '2011010101.php'; + +$processed_upgrades = array_unique($processed_upgrades); +elgg_set_processed_upgrades($processed_upgrades); + +_elgg_upgrade_unlock(); + +forward('upgrade.php'); diff --git a/engine/lib/upgrades/2011021800-1.8_svn-goodbye_walled_garden-083121a656d06894.php b/engine/lib/upgrades/2011021800-1.8_svn-goodbye_walled_garden-083121a656d06894.php new file mode 100644 index 000000000..40b2c71d5 --- /dev/null +++ b/engine/lib/upgrades/2011021800-1.8_svn-goodbye_walled_garden-083121a656d06894.php @@ -0,0 +1,34 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011021800 + * goodbye_walled_garden + * + * Removes the Walled Garden plugin in favor of new system settings + */ + +global $CONFIG; + +$access = elgg_set_ignore_access(TRUE); + +if (elgg_is_active_plugin('walledgarden')) { +	disable_plugin('walledgarden'); +	set_config('allow_registration', FALSE); +	set_config('walled_garden', TRUE); +} else { +	set_config('allow_registration', TRUE); +	set_config('walled_garden', FALSE); +} + +// this was for people who manually set the config option +$disable_registration = elgg_get_config('disable_registration'); +if ($disable_registration !== null) { +	$allow_registration = !$disable_registration; +	elgg_save_config('allow_registration', $allow_registration); + +	$site = elgg_get_site_entity(); +	$query = "DELETE FROM {$CONFIG->dbprefix}config +				WHERE name = 'disable_registration' AND site_guid = $site->guid"; +	delete_data($query); +} + +elgg_set_ignore_access($access); diff --git a/engine/lib/upgrades/2011022000-1.8_svn-custom_profile_fields-390ac967b0bb5665.php b/engine/lib/upgrades/2011022000-1.8_svn-custom_profile_fields-390ac967b0bb5665.php new file mode 100644 index 000000000..7561b84ba --- /dev/null +++ b/engine/lib/upgrades/2011022000-1.8_svn-custom_profile_fields-390ac967b0bb5665.php @@ -0,0 +1,59 @@ +<?php +/** + * Elgg 2011010401 upgrade 00 + * custom_profile_fields + * + * Migrate 1.7 style custom profile fields to 1.8 + */ + +$plugin = elgg_get_plugin_from_id('profile'); + +// plugin not installed +if (!$plugin) { +	return true; +} + +$settings = $plugin->getAllSettings(); +// no fields to migrate +if (!$settings['user_defined_fields']) { +	return true; +} + +$order = array(); +$remove_settings = array(); + +// make sure we have a name and type +foreach ($settings as $k => $v) { +	if (!preg_match('/admin_defined_profile_([0-9]+)/i', $k, $matches)) { +		continue; +	} + +	$i = $matches[1]; +	$type_name = "admin_defined_profile_type_$i"; +	$type = elgg_extract($type_name, $settings, null); + +	if ($type) { +		// field name +		elgg_save_config($k, $v); +		// field value +		elgg_save_config($type_name, $type); + +		$order[] = $i; +		$remove_settings[] = $k; +		$remove_settings[] = $type_name; +	} +} + +if ($order) { +	// these will always need to be in order, but there might be gaps +	ksort($order); + +	$order_str = implode(',', $order); +	elgg_save_config('profile_custom_fields', $order_str); + +	foreach ($remove_settings as $name) { +		$plugin->unsetSetting($name); +	} + +	$plugin->unsetSetting('user_defined_fields'); +}
\ No newline at end of file diff --git a/engine/lib/upgrades/2011030700-1.8_svn-blog_status_metadata-4645225d7b440876.php b/engine/lib/upgrades/2011030700-1.8_svn-blog_status_metadata-4645225d7b440876.php new file mode 100644 index 000000000..fe2af9928 --- /dev/null +++ b/engine/lib/upgrades/2011030700-1.8_svn-blog_status_metadata-4645225d7b440876.php @@ -0,0 +1,24 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011030700 + * blog_status_metadata + * + * Add a "status" metadata entry to every blog entity because in 1.8 you can have status = draft or + * status = published + */ +$ia = elgg_set_ignore_access(true); +$options = array( +	'type' => 'object', +	'subtype' => 'blog', +	'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { +	if (!$entity->status) { +		// create metadata owned by the original owner +		create_metadata($entity->getGUID(), 'status', 'published', '', $entity->owner_guid, +			$entity->access_id); +	} +} +elgg_set_ignore_access($ia);
\ No newline at end of file diff --git a/engine/lib/upgrades/2011031300-1.8_svn-twitter_api-12b832a5a7a3e1bd.php b/engine/lib/upgrades/2011031300-1.8_svn-twitter_api-12b832a5a7a3e1bd.php new file mode 100644 index 000000000..df60892a6 --- /dev/null +++ b/engine/lib/upgrades/2011031300-1.8_svn-twitter_api-12b832a5a7a3e1bd.php @@ -0,0 +1,54 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011031300 + * twitter_api + * + * Updates the database for twitterservice to twitter_api changes. + */ + + +$ia = elgg_set_ignore_access(true); + +// make sure we have updated plugins +elgg_generate_plugin_entities(); + +$show_hidden = access_get_show_hidden_status(); +access_show_hidden_entities(true); + +$db_prefix = elgg_get_config('dbprefix'); +$site_guid = elgg_get_site_entity()->getGUID(); +$old = elgg_get_plugin_from_id('twitterservice'); +$new = elgg_get_plugin_from_id('twitter_api'); +$has_settings = false; + +// if not loaded, don't bother. +if (!$old || !$new) { +	return true; +} + +$settings = array('consumer_key', 'consumer_secret', 'sign_on', 'new_users'); + +foreach ($settings as $setting) { +	$value = $old->getSetting($setting); +	if ($value) { +		$has_settings = true; +		$new->setSetting($setting, $value); +	} +} + +// update the user settings +$q = "UPDATE {$db_prefix}private_settings +	SET name = replace(name, 'twitterservice', 'twitter_api') +	WHERE name like '%twitterservice%'"; + +update_data($q); + +// if there were settings, emit a notice to re-enable twitter_api +if ($has_settings) { +	elgg_add_admin_notice('twitter_api:disabled', elgg_echo('update:twitter_api:deactivated')); +} + +$old->delete(); + +access_show_hidden_entities($show_hidden); +elgg_set_ignore_access($ia);
\ No newline at end of file diff --git a/engine/lib/upgrades/2011031600-1.8_svn-datalist_grows_up-0b8aec5a55cc1e1c.php b/engine/lib/upgrades/2011031600-1.8_svn-datalist_grows_up-0b8aec5a55cc1e1c.php new file mode 100644 index 000000000..379244b36 --- /dev/null +++ b/engine/lib/upgrades/2011031600-1.8_svn-datalist_grows_up-0b8aec5a55cc1e1c.php @@ -0,0 +1,18 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011031600 + * datalist_grows_up + * + * Ups the varchar to 256 for the datalist and config table. + * + * Keeping it as a varchar because of the trailing whitespace trimming it apparently does: + * http://dev.mysql.com/doc/refman/5.0/en/char.html + */ + +$db_prefix = elgg_get_config('dbprefix'); + +$q = "ALTER TABLE {$db_prefix}datalists CHANGE name name VARCHAR(255)"; +update_data($q); + +$q = "ALTER TABLE {$db_prefix}config CHANGE name name VARCHAR(255)"; +update_data($q); diff --git a/engine/lib/upgrades/2011032000-1.8_svn-widgets_arent_plugins-61836261fa280a5c.php b/engine/lib/upgrades/2011032000-1.8_svn-widgets_arent_plugins-61836261fa280a5c.php new file mode 100644 index 000000000..a20970d79 --- /dev/null +++ b/engine/lib/upgrades/2011032000-1.8_svn-widgets_arent_plugins-61836261fa280a5c.php @@ -0,0 +1,10 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011031800 + * widgets_arent_plugins + * + * At some point in Elgg's history subtype widget was registered with class ElggPlugin. + * Fix that. + */ + +update_subtype('object', 'widget', 'ElggWidget'); diff --git a/engine/lib/upgrades/2011032200-1.8_svn-admins_like_widgets-7f19d2783c1680d3.php b/engine/lib/upgrades/2011032200-1.8_svn-admins_like_widgets-7f19d2783c1680d3.php new file mode 100644 index 000000000..592adb403 --- /dev/null +++ b/engine/lib/upgrades/2011032200-1.8_svn-admins_like_widgets-7f19d2783c1680d3.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011032200 + * admins_like_widgets + * + * Give current admins widgets for those pre-1.8 + */ + +$admins = elgg_get_admins(array('limit' => 0)); +foreach ($admins as $admin) { +	// call the admin handler for the make_admin event +	elgg_add_admin_widgets('make_admin', 'user', $admin); +} diff --git a/engine/lib/upgrades/2011052801.php b/engine/lib/upgrades/2011052801.php new file mode 100644 index 000000000..b5a8e1018 --- /dev/null +++ b/engine/lib/upgrades/2011052801.php @@ -0,0 +1,46 @@ +<?php +/** + * Make sure all users have the relationship member_of_site + */ +global $ENTITY_CACHE; +$db_prefix = get_config('dbprefix'); + +$limit = 100; + +$q = "SELECT e.* FROM {$db_prefix}entities e +	WHERE e.type = 'user' AND e.guid NOT IN ( +		SELECT guid_one FROM {$db_prefix}entity_relationships +			WHERE guid_two = 1 AND relationship = 'member_of_site' +		) +	LIMIT $limit"; + +$users = get_data($q); + +while ($users) { +	$ENTITY_CACHE = array(); +	_elgg_invalidate_query_cache(); + +	// do manually to not trigger any events because these aren't new users. +	foreach ($users as $user) { +		$rel_q = "INSERT INTO {$db_prefix}entity_relationships VALUES ( +			'', +			'$user->guid', +			'member_of_site', +			'$user->site_guid', +			'$user->time_created' +		)"; + +		insert_data($rel_q); +	} + +	// every time we run this query we've just reduced the rows it returns by $limit +	// so don't pass an offset. +	$q = "SELECT e.* FROM {$db_prefix}entities e +		WHERE e.type = 'user' AND e.guid NOT IN ( +			SELECT guid_one FROM {$db_prefix}entity_relationships +				WHERE guid_two = 1 AND relationship = 'member_of_site' +			) +		LIMIT $limit"; + +	$users = get_data($q); +}
\ No newline at end of file diff --git a/engine/lib/upgrades/2011061200-1.8b1-sites_need_a_site_guid-6d9dcbf46c0826cc.php b/engine/lib/upgrades/2011061200-1.8b1-sites_need_a_site_guid-6d9dcbf46c0826cc.php new file mode 100644 index 000000000..41ab29998 --- /dev/null +++ b/engine/lib/upgrades/2011061200-1.8b1-sites_need_a_site_guid-6d9dcbf46c0826cc.php @@ -0,0 +1,31 @@ +<?php +/** + * Elgg 1.8b1 upgrade 2011061200 + * sites_need_a_site_guid + * + * Sites did not have a site guid. This causes problems with getting + * metadata on site objects since we default to the current site. + */ + +global $CONFIG; + +$ia = elgg_set_ignore_access(true); +$access_status = access_get_show_hidden_status(); +access_show_hidden_entities(true); + +$options = array( +	'type' => 'site', +	'site_guid' => 0, +	'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { +	if (!$entity->site_guid) { +		update_data("UPDATE {$CONFIG->dbprefix}entities SET site_guid=$entity->guid +				WHERE guid=$entity->guid"); +	} +} + +access_show_hidden_entities($access_status); +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2011092500-1.8.0.1-forum_reply_river_view-5758ce8d86ac56ce.php b/engine/lib/upgrades/2011092500-1.8.0.1-forum_reply_river_view-5758ce8d86ac56ce.php new file mode 100644 index 000000000..3a9200b51 --- /dev/null +++ b/engine/lib/upgrades/2011092500-1.8.0.1-forum_reply_river_view-5758ce8d86ac56ce.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.0.1 upgrade 2011092500 + * forum_reply_river_view + * + * The forum reply river view is in a new location in Elgg 1.8 + */ + +$query = "UPDATE {$CONFIG->dbprefix}river SET view='river/annotation/group_topic_post/reply', +			action_type='reply' +			WHERE view='river/forum/create' AND action_type='create'"; +update_data($query); diff --git a/engine/lib/upgrades/2011123100-1.8.2-fix_friend_river-b17e7ff8345c2269.php b/engine/lib/upgrades/2011123100-1.8.2-fix_friend_river-b17e7ff8345c2269.php new file mode 100644 index 000000000..4dc43cd32 --- /dev/null +++ b/engine/lib/upgrades/2011123100-1.8.2-fix_friend_river-b17e7ff8345c2269.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.2 upgrade 2011123100 + * fix_friend_river + * + * Action type was incorrect due to previoud friends river upgrade + */ + +$query = "UPDATE {$CONFIG->dbprefix}river +			SET action_type='friend' +			WHERE view='river/relationship/friend/create' AND action_type='create'"; +update_data($query); diff --git a/engine/lib/upgrades/2011123101-1.8.2-fix_blog_status-b14c2a0e7b9e7d55.php b/engine/lib/upgrades/2011123101-1.8.2-fix_blog_status-b14c2a0e7b9e7d55.php new file mode 100644 index 000000000..e351c6ac9 --- /dev/null +++ b/engine/lib/upgrades/2011123101-1.8.2-fix_blog_status-b14c2a0e7b9e7d55.php @@ -0,0 +1,25 @@ +<?php +/** + * Elgg 1.8.2 upgrade 2011123101 + * fix_blog_status + * + * Most blog posts did not have their status properly set with 1.8 upgrade so we run + * the blog status upgrade again + */ + +$ia = elgg_set_ignore_access(true); +$options = array( +	'type' => 'object', +	'subtype' => 'blog', +	'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { +	if (!$entity->status) { +		// create metadata owned by the original owner +		create_metadata($entity->getGUID(), 'status', 'published', '', $entity->owner_guid, +			$entity->access_id); +	} +} +elgg_set_ignore_access($ia);
\ No newline at end of file diff --git a/engine/lib/upgrades/2012012000-1.8.3-ip_in_syslog-87fe0f068cf62428.php b/engine/lib/upgrades/2012012000-1.8.3-ip_in_syslog-87fe0f068cf62428.php new file mode 100644 index 000000000..b9514e156 --- /dev/null +++ b/engine/lib/upgrades/2012012000-1.8.3-ip_in_syslog-87fe0f068cf62428.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012012000 + * ip_in_syslog + * + * Adds a field for an IP address in the system log table + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}system_log ADD ip_address VARCHAR(15) NOT NULL AFTER time_created"; + +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2012012100-1.8.3-system_cache-93100e7d55a24a11.php b/engine/lib/upgrades/2012012100-1.8.3-system_cache-93100e7d55a24a11.php new file mode 100644 index 000000000..3a9aae2a1 --- /dev/null +++ b/engine/lib/upgrades/2012012100-1.8.3-system_cache-93100e7d55a24a11.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012012100 + * system_cache + * + * Convert viewpath cache to system cache + */ + +$value = datalist_get('viewpath_cache_enabled'); +datalist_set('system_cache_enabled', $value); + +$query = "DELETE FROM {$CONFIG->dbprefix}datalists WHERE name='viewpath_cache_enabled'"; +delete_data($query); diff --git a/engine/lib/upgrades/2012041800-1.8.3-dont_filter_passwords-c0ca4a18b38ae2bc.php b/engine/lib/upgrades/2012041800-1.8.3-dont_filter_passwords-c0ca4a18b38ae2bc.php new file mode 100644 index 000000000..b82ffbebf --- /dev/null +++ b/engine/lib/upgrades/2012041800-1.8.3-dont_filter_passwords-c0ca4a18b38ae2bc.php @@ -0,0 +1,11 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012041800 + * dont_filter_passwords + * + * Add admin notice that password handling has changed and if  + * users can't login to have them reset their passwords. + */ +elgg_add_admin_notice('dont_filter_passwords', 'Password handling has been updated to be more secure and flexible. ' +	. 'This change may prevent a small number of users from logging in with their existing passwords. ' +	. 'If a user is unable to log in, please advise him or her to reset their password, or reset it as an admin user.'); diff --git a/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php new file mode 100644 index 000000000..780038c32 --- /dev/null +++ b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012041801 + * multiple_user_tokens + * + * Fixes https://github.com/elgg/elgg/issues/4291 + * Removes the unique index on users_apisessions for user_guid and site_guid + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}users_apisessions DROP INDEX user_guid, +	ADD INDEX user_guid (user_guid, site_guid)"; +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php new file mode 100644 index 000000000..8eccf05e2 --- /dev/null +++ b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php @@ -0,0 +1,24 @@ +<?php +/** + * Elgg 1.8.14 upgrade 2013030600 + * update_user_location + * + * Before Elgg 1.8, a location like "London, England" would be stored as an array. + * This script turns that back into a string. + */ + +$ia = elgg_set_ignore_access(true); +$options = array( +	'type' => 'user', +	'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { +	_elgg_invalidate_query_cache(); +	 +	if (is_array($entity->location)) { +		$entity->location = implode(', ', $entity->location); +	} +} +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php new file mode 100644 index 000000000..ee99bdbc8 --- /dev/null +++ b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php @@ -0,0 +1,28 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013051700 + * add_missing_group_index + * + * Some Elgg sites are missing the groups_entity full text index on name and + * description. This checks if it exists and adds it if it does not. + */ + +$db_prefix = elgg_get_config('dbprefix'); + +$full_text_index_exists = false; +$results = get_data("SHOW INDEX FROM {$db_prefix}groups_entity"); +if ($results) { +	foreach ($results as $result) { +		if ($result->Index_type === 'FULLTEXT') { +			$full_text_index_exists = true; +		} +	} +} + +if ($full_text_index_exists == false) { +	$query = "ALTER TABLE {$db_prefix}groups_entity  +		ADD FULLTEXT name_2 (name, description)"; +	if (!update_data($query)) { +		elgg_log("Failed to add full text index to groups_entity table", 'ERROR'); +	} +} diff --git a/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php new file mode 100644 index 000000000..d333a6cd2 --- /dev/null +++ b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013052900 + * ipv6_in_syslog + * + * Upgrade the ip column in system_log to be able to store ipv6 addresses + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}system_log MODIFY COLUMN ip_address varchar(46) NOT NULL"; + +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php new file mode 100644 index 000000000..538d74dd6 --- /dev/null +++ b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php @@ -0,0 +1,16 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013060900 + * site_secret + * + * Description + */ + +$strength = _elgg_get_site_secret_strength(); + +if ($strength !== 'strong') { +	// a new key is needed immediately +	register_translations(elgg_get_root_path() . 'languages/'); + +	elgg_add_admin_notice('weak_site_key', elgg_echo("upgrade:site_secret_warning:$strength")); +} diff --git a/engine/lib/upgrades/create_upgrade.php b/engine/lib/upgrades/create_upgrade.php new file mode 100644 index 000000000..b34f31b7e --- /dev/null +++ b/engine/lib/upgrades/create_upgrade.php @@ -0,0 +1,152 @@ +<?php +/** + * Creates an upgrade file for Elgg. + * + * Run this from the command line: + * 	php create_upgrade.php upgrade_name + */ + +error_reporting(E_NOTICE); + +// only allow from the command line. +if (php_sapi_name() != 'cli') { +	die('Upgrades can only be created from the command line.'); +} + +if (count($argv) < 2) { +	elgg_create_upgrade_show_usage('No upgrade name.'); +} + +$name = $argv[1]; + +if (strlen($name) > 24) { +	elgg_create_upgrade_show_usage('Upgrade names cannot be longer than 24 characters.'); +} + +require_once '../../../version.php'; +require_once '../elgglib.php'; +$upgrade_path = dirname(__FILE__); + +$upgrade_name = strtolower($name); +$upgrade_name = str_replace(array(' ', '-'), '_', $upgrade_name); +$upgrade_release = str_replace(array(' ', '-'), '_', $release); +$time = time(); +$upgrade_rnd = substr(md5($time), 0, 16); +$upgrade_date = date('Ymd', $time); + +// determine the inc count +$upgrade_inc = 0; +$files = elgg_get_file_list($upgrade_path); +sort($files); + +foreach ($files as $filename) { +	$filename = basename($filename); +	$date = (int)substr($filename, 0, 8); +	$inc = (int)substr($filename, 8, 2); + +	if ($upgrade_date == $date) { +		if ($inc >= $upgrade_inc) { +			$upgrade_inc = $inc + 1; +		} +	} +} + +// zero-pad +// if there are more than 10 upgrades in a day, someone needs talking to. +if ($upgrade_inc < 10) { +	$upgrade_inc = "0$upgrade_inc"; +} + +$upgrade_version = $upgrade_date . $upgrade_inc; + +// make filename +if (substr($release, 0, 3) == '1.7') { +	// 1.7 upgrades are YYYYMMDDXX +	$upgrade_name = $upgrade_version . '.php'; +} else { +	// 1.8+ upgrades are YYYYMMDDXX-release-friendly_name-rnd +	$upgrade_name = $upgrade_version . "-$upgrade_release-$name-$upgrade_rnd.php"; +} + +$upgrade_file = $upgrade_path . '/' . $upgrade_name; + +if (is_file($upgrade_file)) { +	elgg_create_upgrade_show_usage("Upgrade file $upgrade_file already exists. This script has failed you."); +} + +$upgrade_code = <<<___UPGRADE +<?php +/** + * Elgg $release upgrade $upgrade_version + * $name + * + * Description + */ + +// upgrade code here. + +___UPGRADE; + +$h = fopen($upgrade_file, 'wb'); + +if (!$h) { +	die("Could not open file $upgrade_file"); +} + +if (!fwrite($h, $upgrade_code)) { +	die("Could not write to $upgrade_file"); +} else { +	elgg_set_version_dot_php_version($upgrade_version); +	echo <<<___MSG + +Created upgrade file and updated version.php. + +Upgrade file: $upgrade_name +Version:      $upgrade_version + +___MSG; +} + +fclose($h); + + +function elgg_set_version_dot_php_version($version) { +	$file = '../../../version.php'; +	$h = fopen($file, 'r+b'); + +	if (!$h) { +		return false; +	} + +	$out = ''; + +	while (($line = fgets($h)) !== false) { +		$find = "/\\\$version[ ]?=[ ]?[0-9]{10};/"; +		$replace = "\$version = $version;"; +		$out .= preg_replace($find, $replace, $line); +	} + +	rewind($h); + +	fwrite($h, $out); +	fclose($h); +	return true; +} + +/** + * Shows the usage for the create_upgrade script and dies(). + * + * @param string $msg Optional message to display + * @return void + */ +function elgg_create_upgrade_show_usage($msg = '') { +	$text = <<<___MSG +$msg + +Example: +	php create_upgrade.php my_upgrade + +___MSG; + +	die($text); +} diff --git a/engine/lib/user_settings.php b/engine/lib/user_settings.php new file mode 100644 index 000000000..0e36dc46d --- /dev/null +++ b/engine/lib/user_settings.php @@ -0,0 +1,360 @@ +<?php +/** + * Elgg user settings functions. + * Functions for adding and manipulating options on the user settings panel. + * + * @package Elgg.Core + * @subpackage Settings.User + */ + +/** + * Saves user settings. + * + * @todo this assumes settings are coming in on a GET/POST request + * + * @note This is a handler for the 'usersettings:save', 'user' plugin hook + * + * @return void + * @access private + */ +function users_settings_save() { +	elgg_set_user_language(); +	elgg_set_user_password(); +	elgg_set_user_default_access(); +	elgg_set_user_name(); +	elgg_set_user_email(); +} + +/** + * Set a user's password + *  + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_password() { +	$current_password = get_input('current_password', null, false); +	$password = get_input('password', null, false); +	$password2 = get_input('password2', null, false); +	$user_guid = get_input('guid'); + +	if (!$user_guid) { +		$user = elgg_get_logged_in_user_entity(); +	} else { +		$user = get_entity($user_guid); +	} + +	if ($user && $password) { +		// let admin user change anyone's password without knowing it except his own. +		if (!elgg_is_admin_logged_in() || elgg_is_admin_logged_in() && $user->guid == elgg_get_logged_in_user_guid()) { +			$credentials = array( +				'username' => $user->username, +				'password' => $current_password +			); + +			try { +				pam_auth_userpass($credentials); +			} catch (LoginException $e) { +				register_error(elgg_echo('LoginException:ChangePasswordFailure')); +				return false; +			} +		} + +		try { +			$result = validate_password($password); +		} catch (RegistrationException $e) { +			register_error($e->getMessage()); +			return false; +		} + +		if ($result) { +			if ($password == $password2) { +				$user->salt = generate_random_cleartext_password(); // Reset the salt +				$user->password = generate_user_password($user, $password); +				if ($user->save()) { +					system_message(elgg_echo('user:password:success')); +					return true; +				} else { +					register_error(elgg_echo('user:password:fail')); +				} +			} else { +				register_error(elgg_echo('user:password:fail:notsame')); +			} +		} else { +			register_error(elgg_echo('user:password:fail:tooshort')); +		} +	} else { +		// no change +		return null; +	} +	 +	return false; +} + +/** + * Set a user's display name + *  + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_name() { +	$name = strip_tags(get_input('name')); +	$user_id = get_input('guid'); + +	if (!$user_id) { +		$user = elgg_get_logged_in_user_entity(); +	} else { +		$user = get_entity($user_id); +	} + +	if (elgg_strlen($name) > 50) { +		register_error(elgg_echo('user:name:fail')); +		return false; +	} + +	if (($user) && ($user->canEdit()) && ($name)) { +		if ($name != $user->name) { +			$user->name = $name; +			if ($user->save()) { +				system_message(elgg_echo('user:name:success')); +				return true; +			} else { +				register_error(elgg_echo('user:name:fail')); +			} +		} else { +			// no change +			return null; +		} +	} else { +		register_error(elgg_echo('user:name:fail')); +	} +	return false; +} + +/** + * Set a user's language + *  + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_language() { +	$language = get_input('language'); +	$user_id = get_input('guid'); + +	if (!$user_id) { +		$user = elgg_get_logged_in_user_entity(); +	} else { +		$user = get_entity($user_id); +	} + +	if (($user) && ($language)) { +		if (strcmp($language, $user->language) != 0) { +			$user->language = $language; +			if ($user->save()) { +				system_message(elgg_echo('user:language:success')); +				return true; +			} else { +				register_error(elgg_echo('user:language:fail')); +			} +		} else { +			// no change +			return null; +		} +	} else { +		register_error(elgg_echo('user:language:fail')); +	} +	return false; +} + +/** + * Set a user's email address + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_email() { +	$email = get_input('email'); +	$user_id = get_input('guid'); + +	if (!$user_id) { +		$user = elgg_get_logged_in_user_entity(); +	} else { +		$user = get_entity($user_id); +	} + +	if (!is_email_address($email)) { +		register_error(elgg_echo('email:save:fail')); +		return false; +	} + +	if ($user) { +		if (strcmp($email, $user->email) != 0) { +			if (!get_user_by_email($email)) { +				if ($user->email != $email) { + +					$user->email = $email; +					if ($user->save()) { +						system_message(elgg_echo('email:save:success')); +						return true; +					} else { +						register_error(elgg_echo('email:save:fail')); +					} +				} +			} else { +				register_error(elgg_echo('registration:dupeemail')); +			} +		} else { +			// no change +			return null; +		} +	} else { +		register_error(elgg_echo('email:save:fail')); +	} +	return false; +} + +/** + * Set a user's default access level + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_default_access() { + +	if (!elgg_get_config('allow_user_default_access')) { +		return false; +	} + +	$default_access = get_input('default_access'); +	$user_id = get_input('guid'); + +	if (!$user_id) { +		$user = elgg_get_logged_in_user_entity(); +	} else { +		$user = get_entity($user_id); +	} + +	if ($user) { +		$current_default_access = $user->getPrivateSetting('elgg_default_access'); +		if ($default_access !== $current_default_access) { +			if ($user->setPrivateSetting('elgg_default_access', $default_access)) { +				system_message(elgg_echo('user:default_access:success')); +				return true; +			} else { +				register_error(elgg_echo('user:default_access:fail')); +			} +		} else { +			// no change +			return null; +		} +	} else { +		register_error(elgg_echo('user:default_access:fail')); +	} + +	return false; +} + +/** + * Set up the menu for user settings + * + * @return void + * @access private + */ +function usersettings_pagesetup() { +	$user = elgg_get_page_owner_entity(); + +	if ($user && elgg_get_context() == "settings") { +		$params = array( +			'name' => '1_account', +			'text' => elgg_echo('usersettings:user:opt:linktext'), +			'href' => "settings/user/{$user->username}", +		); +		elgg_register_menu_item('page', $params); +		$params = array( +			'name' => '1_plugins', +			'text' => elgg_echo('usersettings:plugins:opt:linktext'), +			'href' => "settings/plugins/{$user->username}", +		); +		elgg_register_menu_item('page', $params); +		$params = array( +			'name' => '1_statistics', +			'text' => elgg_echo('usersettings:statistics:opt:linktext'), +			'href' => "settings/statistics/{$user->username}", +		); +		elgg_register_menu_item('page', $params); +	} +} + +/** + * Page handler for user settings + * + * @param array $page Pages array + * + * @return bool + * @access private + */ +function usersettings_page_handler($page) { +	global $CONFIG; + +	if (!isset($page[0])) { +		$page[0] = 'user'; +	} + +	if (isset($page[1])) { +		$user = get_user_by_username($page[1]); +		elgg_set_page_owner_guid($user->guid); +	} else { +		$user = elgg_get_logged_in_user_entity(); +		elgg_set_page_owner_guid($user->guid); +	} + +	elgg_push_breadcrumb(elgg_echo('settings'), "settings/user/$user->username"); + +	switch ($page[0]) { +		case 'statistics': +			elgg_push_breadcrumb(elgg_echo('usersettings:statistics:opt:linktext')); +			$path = $CONFIG->path . "pages/settings/statistics.php"; +			break; +		case 'plugins': +			elgg_push_breadcrumb(elgg_echo('usersettings:plugins:opt:linktext')); +			$path = $CONFIG->path . "pages/settings/tools.php"; +			break; +		case 'user': +			$path = $CONFIG->path . "pages/settings/account.php"; +			break; +	} + +	if (isset($path)) { +		require $path; +		return true; +	} +	return false; +} + +/** + * Initialize the user settings library + * + * @return void + * @access private + */ +function usersettings_init() { +	elgg_register_page_handler('settings', 'usersettings_page_handler'); + +	elgg_register_plugin_hook_handler('usersettings:save', 'user', 'users_settings_save'); + +	elgg_register_action("usersettings/save"); + +	// extend the account settings form +	elgg_extend_view('forms/account/settings', 'core/settings/account/name', 100); +	elgg_extend_view('forms/account/settings', 'core/settings/account/password', 100); +	elgg_extend_view('forms/account/settings', 'core/settings/account/email', 100); +	elgg_extend_view('forms/account/settings', 'core/settings/account/language', 100); +	elgg_extend_view('forms/account/settings', 'core/settings/account/default_access', 100); +} + +elgg_register_event_handler('init', 'system', 'usersettings_init'); +elgg_register_event_handler('pagesetup', 'system', 'usersettings_pagesetup'); diff --git a/engine/lib/users.php b/engine/lib/users.php new file mode 100644 index 000000000..a8fb9121c --- /dev/null +++ b/engine/lib/users.php @@ -0,0 +1,1611 @@ +<?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"); +			_elgg_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"); +			_elgg_invalidate_cache_for_entity($user_guid); +			return $r; +		} + +		return FALSE; +	} + +	return FALSE; +} + +/** + * Get the sites this user is part of + * + * @param int $user_guid The user's GUID + * @param int $limit     Number of results to return + * @param int $offset    Any indexing offset + * + * @return ElggSite[]|false 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, +		'type' => '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) { +	$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 ElggUser[]|false Either an array of ElggUsers or false, depending on success + */ +function get_user_friends($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$offset = 0) { + +	return elgg_get_entities_from_relationship(array( +		'relationship' => 'friend', +		'relationship_guid' => $user_guid, +		'type' => 'user', +		'subtype' => $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 ElggUser[]|false Either an array of ElggUsers or false, depending on success + */ +function get_user_friends_of($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$offset = 0) { + +	return elgg_get_entities_from_relationship(array( +		'relationship' => 'friend', +		'relationship_guid' => $user_guid, +		'inverse_relationship' => TRUE, +		'type' => 'user', +		'subtype' => $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 ElggObject[]|false 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; + +	// Fixes #6052. Username is frequently sniffed from the path info, which, +	// unlike $_GET, is not URL decoded. If the username was not URL encoded, +	// this is harmless. +	$username = rawurldecode($username); + +	$username = sanitise_string($username); +	$access = get_access_sql_suffix('e'); + +	// Caching +	if ((isset($USERNAME_TO_GUID_MAP_CACHE[$username])) +			&& (_elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]))) { +		return _elgg_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])) +	&& (_elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]))) { + +		return _elgg_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) { +	$user_guid = (int)$user_guid; + +	$user = get_entity($user_guid); +	if ($user instanceof ElggUser) { +		// generate code +		$code = generate_random_cleartext_password(); +		$user->setPrivateSetting('passwd_conf_code', $code); + +		// generate link +		$link = elgg_get_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, elgg_get_site_entity()->guid, +			elgg_echo('email:resetreq:subject'), $email, array(), '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) { +	$user = get_entity($user_guid); +	if ($user instanceof ElggUser) { +		$ia = elgg_set_ignore_access(); + +		$user->salt = generate_random_cleartext_password(); +		$hash = generate_user_password($user, $password);		 +		$user->password = $hash; +		$result = (bool)$user->save(); + +		elgg_set_ignore_access($ia); + +		return $result; +	} + +	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 instanceof ElggUser) { +		$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, array(), '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 = htmlspecialchars($msg, ENT_QUOTES, '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 + * @throws RegistrationException + */ +function register_user($username, $password, $name, $email, +$allow_multiple_emails = false, $friend_guid = 0, $invitecode = '') { + +	// 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 null; +	} + +	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($segments, $handler) { +	elgg_set_context('friends'); +	 +	if (isset($segments[0]) && $user = get_user_by_username($segments[0])) { +		elgg_set_page_owner_guid($user->getGUID()); +	} +	if (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) { +	gatekeeper(); +	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 void + * @access private + */ +function user_create_hook_add_site_relationship($event, $object_type, $object) { +	add_entity_relationship($object->getGUID(), 'member_of_site', elgg_get_site_entity()->guid); +} + +/** + * Serves the user's avatar + * + * @param string $hook + * @param string $entity_type + * @param string $returnvalue + * @param array  $params + * @return string + * @access private + */ +function user_avatar_hook($hook, $entity_type, $returnvalue, $params) { +	$user = $params['entity']; +	$size = $params['size']; + +	if (isset($user->icontime)) { +		return "avatar/view/$user->username/$size/$user->icontime"; +	} else { +		return "_graphics/icons/user/default{$size}.gif"; +	} +} + +/** + * Setup the default user hover menu + * @access private + */ +function elgg_user_hover_menu($hook, $type, $return, $params) { +	$user = $params['entity']; +	/* @var ElggUser $user */ + +	if (elgg_is_logged_in()) { +		if (elgg_get_logged_in_user_guid() != $user->guid) { +			if ($user->isFriend()) { +				$url = "action/friends/remove?friend={$user->guid}"; +				$text = elgg_echo('friend:remove'); +				$name = 'remove_friend'; +			} else { +				$url = "action/friends/add?friend={$user->guid}"; +				$text = elgg_echo('friend:add'); +				$name = 'add_friend'; +			} +			$url = elgg_add_action_tokens_to_url($url); +			$item = new ElggMenuItem($name, $text, $url); +			$item->setSection('action'); +			$return[] = $item; +		} else { +			$url = "profile/$user->username/edit"; +			$item = new ElggMenuItem('profile:edit', elgg_echo('profile:edit'), $url); +			$item->setSection('action'); +			$return[] = $item; + +			$url = "avatar/edit/$user->username"; +			$item = new ElggMenuItem('avatar:edit', elgg_echo('avatar:edit'), $url); +			$item->setSection('action'); +			$return[] = $item; +		} +	} + +	// prevent admins from banning or deleting themselves +	if (elgg_get_logged_in_user_guid() == $user->guid) { +		return $return; +	} + +	if (elgg_is_admin_logged_in()) { +		$actions = array(); +		if (!$user->isBanned()) { +			$actions[] = 'ban'; +		} else { +			$actions[] = 'unban'; +		} +		$actions[] = 'delete'; +		$actions[] = 'resetpassword'; +		if (!$user->isAdmin()) { +			$actions[] = 'makeadmin'; +		} else { +			$actions[] = 'removeadmin'; +		} + +		foreach ($actions as $action) { +			$url = "action/admin/user/$action?guid={$user->guid}"; +			$url = elgg_add_action_tokens_to_url($url); +			$item = new ElggMenuItem($action, elgg_echo($action), $url); +			$item->setSection('admin'); +			$item->setLinkClass('elgg-requires-confirmation'); + +			$return[] = $item; +		} + +		$url = "profile/$user->username/edit"; +		$item = new ElggMenuItem('profile:edit', elgg_echo('profile:edit'), $url); +		$item->setSection('admin'); +		$return[] = $item; + +		$url = "settings/user/$user->username"; +		$item = new ElggMenuItem('settings:edit', elgg_echo('settings:edit'), $url); +		$item->setSection('admin'); +		$return[] = $item; +	} + +	return $return; +} + +/** + * Setup the menu shown with an entity + * + * @param string $hook + * @param string $type + * @param array $return + * @param array $params + * @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; +	} +	/* @var ElggUser $entity */ + +	if ($entity->isBanned()) { +		$banned = elgg_echo('banned'); +		$options = array( +			'name' => 'banned', +			'text' => "<span>$banned</span>", +			'href' => false, +			'priority' => 0, +		); +		$return = array(ElggMenuItem::factory($options)); +	} else { +		$return = array(); +		if (isset($entity->location)) { +			$location = htmlspecialchars($entity->location, ENT_QUOTES, 'UTF-8', false); +			$options = array( +				'name' => 'location', +				'text' => "<span>$location</span>", +				'href' => false, +				'priority' => 150, +			); +			$return[] = ElggMenuItem::factory($options); +		} +	} + +	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 string $hook   unit_test + * @param string $type   system + * @param mixed  $value  Array of tests + * @param mixed  $params Params + * + * @return array + * @access private + */ +function users_test($hook, $type, $value, $params) { +	global $CONFIG; +	$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..1142461fe --- /dev/null +++ b/engine/lib/views.php @@ -0,0 +1,1665 @@ +<?php +/** + * Elgg's view system. + * + * The view system is the primary templating engine in Elgg and renders + * all output.  Views are short, parameterised PHP scripts for displaying + * output that can be regsitered, overridden, or extended.  The view type + * determines the output format and location of the files that renders the view. + * + * Elgg uses a two step process to render full output: first + * content-specific elements are rendered, then the resulting + * content is inserted into a layout and displayed.  This makes it + * easy to maintain a consistent look on all pages. + * + * A view corresponds to a single file on the filesystem and the views + * name is its directory structure.  A file in + * <code>mod/plugins/views/default/myplugin/example.php</code> + * is called by saying (with the default viewtype): + * <code>echo elgg_view('myplugin/example');</code> + * + * View names that are registered later override those that are + * registered earlier.  For plugins this corresponds directly + * to their load order: views in plugins lower in the list override + * those higher in the list. + * + * Plugin views belong in the views/ directory under an appropriate + * viewtype.  Views are automatically registered. + * + * Views can be embedded-you can call a view from within a view. + * Views can also be prepended or extended by any other view. + * + * Any view can extend any other view if registered with + * {@link elgg_extend_view()}. + * + * View types are set by passing $_REQUEST['view'].  The view type + * 'default' is a standard HTML view.  Types can be defined on the fly + * and you can get the current view type with {@link get_current_view()}. + * + * @internal Plugin views are autoregistered before their init functions + * are called, so the init order doesn't affect views. + * + * @internal The file that determines the output of the view is the last + * registered by {@link elgg_set_view_location()}. + * + * @package Elgg.Core + * @subpackage Views + * @link http://docs.elgg.org/Views + */ + +/** + * The view type override. + * + * @global string $CURRENT_SYSTEM_VIEWTYPE + * @see elgg_set_viewtype() + */ +global $CURRENT_SYSTEM_VIEWTYPE; +$CURRENT_SYSTEM_VIEWTYPE = ""; + +/** + * Manually set the viewtype. + * + * View types are detected automatically.  This function allows + * you to force subsequent views to use a different viewtype. + * + * @tip Call elgg_set_viewtype() with no parameter to reset. + * + * @param string $viewtype The view type, e.g. 'rss', or 'default'. + * + * @return bool + * @link http://docs.elgg.org/Views/Viewtype + * @example views/viewtype.php + */ +function elgg_set_viewtype($viewtype = "") { +	global $CURRENT_SYSTEM_VIEWTYPE; + +	$CURRENT_SYSTEM_VIEWTYPE = $viewtype; + +	return true; +} + +/** + * Return the current view type. + * + * View types are automatically detected and can be set with $_REQUEST['view'] + * or {@link elgg_set_viewtype()}. + * + * @internal View type is determined in this order: + *  - $CURRENT_SYSTEM_VIEWTYPE Any overrides by {@link elgg_set_viewtype()} + *  - $CONFIG->view  The default view as saved in the DB. + *  - $_SESSION['view'] + * + * @return string The view. + * @see elgg_set_viewtype() + * @link http://docs.elgg.org/Views + * @todo This function's sessions stuff needs rewritten, removed, or explained. + */ +function elgg_get_viewtype() { +	global $CURRENT_SYSTEM_VIEWTYPE, $CONFIG; + +	if ($CURRENT_SYSTEM_VIEWTYPE != "") { +		return $CURRENT_SYSTEM_VIEWTYPE; +	} + +	$viewtype = get_input('view', '', false); +	if (is_string($viewtype) && $viewtype !== '') { +		// only word characters allowed. +		if (!preg_match('/\W/', $viewtype)) { +			return $viewtype; +		} +	} + +	if (!empty($CONFIG->view)) { +		return $CONFIG->view; +	} + +	return 'default'; +} + +/** + * Register a view type as valid. + * + * @param string $view_type The view type to register + * @return bool + */ +function elgg_register_viewtype($view_type) { +	global $CONFIG; + +	if (!isset($CONFIG->view_types) || !is_array($CONFIG->view_types)) { +		$CONFIG->view_types = array(); +	} + +	if (!in_array($view_type, $CONFIG->view_types)) { +		$CONFIG->view_types[] = $view_type; +	} + +	return true; +} + +/** + * Checks if $view_type is valid on this installation. + * + * @param string $view_type View type + * + * @return bool + * @since 1.7.2 + * @access private + */ +function elgg_is_valid_view_type($view_type) { +	global $CONFIG; + +	if (!isset($CONFIG->view_types) || !is_array($CONFIG->view_types)) { +		return FALSE; +	} + +	return in_array($view_type, $CONFIG->view_types); +} + +/** + * Register a viewtype to fall back to a default view if a view isn't + * found for that viewtype. + * + * @tip This is useful for alternate html viewtypes (such as for mobile devices). + * + * @param string $viewtype The viewtype to register + * + * @return void + * @since 1.7.2 + * @example views/viewtype_fallback.php Fallback from mobile to default. + */ +function elgg_register_viewtype_fallback($viewtype) { +	global $CONFIG; + +	if (!isset($CONFIG->viewtype)) { +		$CONFIG->viewtype = new stdClass; +	} + +	if (!isset($CONFIG->viewtype->fallback)) { +		$CONFIG->viewtype->fallback = array(); +	} + +	$CONFIG->viewtype->fallback[] = $viewtype; +} + +/** + * Checks if a viewtype falls back to default. + * + * @param string $viewtype Viewtype + * + * @return boolean + * @since 1.7.2 + */ +function elgg_does_viewtype_fallback($viewtype) { +	global $CONFIG; + +	if (isset($CONFIG->viewtype) && isset($CONFIG->viewtype->fallback)) { +		return in_array($viewtype, $CONFIG->viewtype->fallback); +	} + +	return FALSE; +} + +/** + * Register a view to be available for ajax calls + * + * @param string $view The view name + * @return void + * @since 1.8.3 + */ +function elgg_register_ajax_view($view) { +	global $CONFIG; + +	if (!isset($CONFIG->allowed_ajax_views)) { +		$CONFIG->allowed_ajax_views = array(); +	} + +	$CONFIG->allowed_ajax_views[$view] = true; +} + +/** + * Unregister a view for ajax calls + * + * @param string $view The view name + * @return void + * @since 1.8.3 + */ +function elgg_unregister_ajax_view($view) { +	global $CONFIG; + +	if (isset($CONFIG->allowed_ajax_views[$view])) { +		unset($CONFIG->allowed_ajax_views[$view]); +	} +} + +/** + * Returns the file location for a view. + * + * @warning This doesn't check if the file exists, but only + * constructs (or extracts) the path and returns it. + * + * @param string $view     The view. + * @param string $viewtype The viewtype + * + * @return string + */ +function elgg_get_view_location($view, $viewtype = '') { +	global $CONFIG; + +	if (empty($viewtype)) { +		$viewtype = elgg_get_viewtype(); +	} + +	if (!isset($CONFIG->views->locations[$viewtype][$view])) { +		if (!isset($CONFIG->viewpath)) { +			return dirname(dirname(dirname(__FILE__))) . "/views/"; +		} else { +			return $CONFIG->viewpath; +		} +	} else { +		return $CONFIG->views->locations[$viewtype][$view]; +	} +} + +/** + * Set an alternative base location for a view. + * + * Views are expected to be in plugin_name/views/.  This function can + * be used to change that location. + * + * @internal Core view locations are stored in $CONFIG->viewpath. + * + * @tip This is useful to optionally register views in a plugin. + * + * @param string $view     The name of the view + * @param string $location The base location path + * @param string $viewtype The view type + * + * @return void + */ +function elgg_set_view_location($view, $location, $viewtype = '') { +	global $CONFIG; + +	if (empty($viewtype)) { +		$viewtype = 'default'; +	} + +	if (!isset($CONFIG->views)) { +		$CONFIG->views = new stdClass; +	} + +	if (!isset($CONFIG->views->locations)) { +		$CONFIG->views->locations = array($viewtype => array($view => $location)); + +	} else if (!isset($CONFIG->views->locations[$viewtype])) { +		$CONFIG->views->locations[$viewtype] = array($view => $location); + +	} else { +		$CONFIG->views->locations[$viewtype][$view] = $location; +	} +} + +/** + * Returns whether the specified view exists + * + * @note If $recurse is true, also checks if a view exists only as an extension. + * + * @param string $view     The view name + * @param string $viewtype If set, forces the viewtype + * @param bool   $recurse  If false, do not check extensions + * + * @return bool + */ +function elgg_view_exists($view, $viewtype = '', $recurse = true) { +	global $CONFIG; + +	// Detect view type +	if (empty($viewtype)) { +		$viewtype = elgg_get_viewtype(); +	} + +	if (!isset($CONFIG->views->locations[$viewtype][$view])) { +		if (!isset($CONFIG->viewpath)) { +			$location = dirname(dirname(dirname(__FILE__))) . "/views/"; +		} else { +			$location = $CONFIG->viewpath; +		} +	} else { +		$location = $CONFIG->views->locations[$viewtype][$view]; +	} + +	if (file_exists("{$location}{$viewtype}/{$view}.php")) { +		return true; +	} + +	// If we got here then check whether this exists as an extension +	// We optionally recursively check whether the extended view exists also for the viewtype +	if ($recurse && isset($CONFIG->views->extensions[$view])) { +		foreach ($CONFIG->views->extensions[$view] as $view_extension) { +			// do not recursively check to stay away from infinite loops +			if (elgg_view_exists($view_extension, $viewtype, false)) { +				return true; +			} +		} +	} + +	// Now check if the default view exists if the view is registered as a fallback +	if ($viewtype != 'default' && elgg_does_viewtype_fallback($viewtype)) { +		return elgg_view_exists($view, 'default'); +	} + +	return false; +} + +/** + * Return a parsed view. + * + * Views are rendered by a template handler and returned as strings. + * + * Views are called with a special $vars variable set, + * which includes any variables passed as the second parameter. + * For backward compatbility, the following variables are also set but we + * recommend that you do not use them: + *  - $vars['config'] The $CONFIG global. (Use {@link elgg_get_config()} instead). + *  - $vars['url'] The site URL. (use {@link elgg_get_site_url()} instead). + *  - $vars['user'] The logged in user. (use {@link elgg_get_logged_in_user_entity()} instead). + * + * Custom template handlers can be set with {@link set_template_handler()}. + * + * The output of views can be intercepted by registering for the + * view, $view_name plugin hook. + * + * @warning Any variables in $_SESSION will override passed vars + * upon name collision.  See https://github.com/Elgg/Elgg/issues/2124 + * + * @param string  $view     The name and location of the view to use + * @param array   $vars     Variables to pass to the view. + * @param boolean $bypass   If set to true, elgg_view will bypass any specified + *                          alternative template handler; by default, it will + *                          hand off to this if requested (see set_template_handler) + * @param boolean $ignored  This argument is ignored and will be removed eventually + * @param string  $viewtype If set, forces the viewtype for the elgg_view call to be + *                          this value (default: standard detection) + * + * @return string The parsed view + * @see set_template_handler() + * @example views/elgg_view.php + * @link http://docs.elgg.org/View + */ +function elgg_view($view, $vars = array(), $bypass = false, $ignored = false, $viewtype = '') { +	global $CONFIG; + +	if (!is_string($view) || !is_string($viewtype)) { +		elgg_log("View and Viewtype in views must be a strings: $view", 'NOTICE'); +		return ''; +	} +	// basic checking for bad paths +	if (strpos($view, '..') !== false) { +		return ''; +	} + +	if (!is_array($vars)) { +		elgg_log("Vars in views must be an array: $view", 'ERROR'); +		$vars = array(); +	} + +	// Get the current viewtype +	if ($viewtype === '') { +		$viewtype = elgg_get_viewtype(); +	} elseif (preg_match('/\W/', $viewtype)) { +		// Viewtypes can only be alphanumeric +		return ''; +	} + +	$view_orig = $view; + +	// Trigger the pagesetup event +	if (!isset($CONFIG->pagesetupdone) && $CONFIG->boot_complete) { +		$CONFIG->pagesetupdone = true; +		elgg_trigger_event('pagesetup', 'system'); +	} + +	// @warning - plugin authors: do not expect user, config, and url to be +	// set by elgg_view() in the future. Instead, use elgg_get_logged_in_user_entity(), +	// elgg_get_config(), and elgg_get_site_url() in your views. +	if (!isset($vars['user'])) { +		$vars['user'] = elgg_get_logged_in_user_entity(); +	} +	if (!isset($vars['config'])) { +		$vars['config'] = $CONFIG; +	} +	if (!isset($vars['url'])) { +		$vars['url'] = elgg_get_site_url(); +	} + +	// full_view is the new preferred key for full view on entities @see elgg_view_entity() +	// check if full_view is set because that means we've already rewritten it and this is +	// coming from another view passing $vars directly. +	if (isset($vars['full']) && !isset($vars['full_view'])) { +		elgg_deprecated_notice("Use \$vars['full_view'] instead of \$vars['full']", 1.8, 2); +		$vars['full_view'] = $vars['full']; +	} +	if (isset($vars['full_view'])) { +		$vars['full'] = $vars['full_view']; +	} + +	// internalname => name (1.8) +	if (isset($vars['internalname']) && !isset($vars['__ignoreInternalname']) && !isset($vars['name'])) { +		elgg_deprecated_notice('You should pass $vars[\'name\'] now instead of $vars[\'internalname\']', 1.8, 2); +		$vars['name'] = $vars['internalname']; +	} elseif (isset($vars['name'])) { +		if (!isset($vars['internalname'])) { +			$vars['__ignoreInternalname'] = ''; +		} +		$vars['internalname'] = $vars['name']; +	} + +	// internalid => id (1.8) +	if (isset($vars['internalid']) && !isset($vars['__ignoreInternalid']) && !isset($vars['name'])) { +		elgg_deprecated_notice('You should pass $vars[\'id\'] now instead of $vars[\'internalid\']', 1.8, 2); +		$vars['id'] = $vars['internalid']; +	} elseif (isset($vars['id'])) { +		if (!isset($vars['internalid'])) { +			$vars['__ignoreInternalid'] = ''; +		} +		$vars['internalid'] = $vars['id']; +	} + +	// If it's been requested, pass off to a template handler instead +	if ($bypass == false && isset($CONFIG->template_handler) && !empty($CONFIG->template_handler)) { +		$template_handler = $CONFIG->template_handler; +		if (is_callable($template_handler)) { +			return call_user_func($template_handler, $view, $vars); +		} +	} + +	// Set up any extensions to the requested view +	if (isset($CONFIG->views->extensions[$view])) { +		$viewlist = $CONFIG->views->extensions[$view]; +	} else { +		$viewlist = array(500 => $view); +	} + +	// Start the output buffer, find the requested view file, and execute it +	ob_start(); + +	foreach ($viewlist as $priority => $view) { + +		$view_location = elgg_get_view_location($view, $viewtype); +		$view_file = "$view_location$viewtype/$view.php"; + +		// try to include view +		if (!file_exists($view_file) || !include($view_file)) { +			// requested view does not exist +			$error = "$viewtype/$view view does not exist."; + +			// attempt to load default view +			if ($viewtype !== 'default' && elgg_does_viewtype_fallback($viewtype)) { + +				$default_location = elgg_get_view_location($view, 'default'); +				$default_view_file = "{$default_location}default/$view.php"; + +				if (file_exists($default_view_file) && include($default_view_file)) { +					// default view found +					$error .= " Using default/$view instead."; +				} else { +					// no view found at all +					$error = "Neither $viewtype/$view nor default/$view view exists."; +				} +			} + +			// log warning +			elgg_log($error, 'NOTICE'); +		} +	} + +	// Save the output buffer into the $content variable +	$content = ob_get_clean(); + +	// Plugin hook +	$params = array('view' => $view_orig, 'vars' => $vars, 'viewtype' => $viewtype); +	$content = elgg_trigger_plugin_hook('view', $view_orig, $params, $content); + +	// backward compatibility with less granular hook will be gone in 2.0 +	$content_tmp = elgg_trigger_plugin_hook('display', 'view', $params, $content); + +	if ($content_tmp !== $content) { +		$content = $content_tmp; +		elgg_deprecated_notice('The display:view plugin hook is deprecated by view:view_name', 1.8); +	} + +	return $content; +} + +/** + * Extends a view with another view. + * + * The output of any view can be prepended or appended to any other view. + * + * The default action is to append a view.  If the priority is less than 500, + * the output of the extended view will be appended to the original view. + * + * Priority can be specified and affects the order in which extensions + * are appended or prepended. + * + * @internal View extensions are stored in + * $CONFIG->views->extensions[$view][$priority] = $view_extension + * + * @param string $view           The view to extend. + * @param string $view_extension This view is added to $view + * @param int    $priority       The priority, from 0 to 1000, + *                               to add at (lowest numbers displayed first) + * + * @return void + * @since 1.7.0 + * @link http://docs.elgg.org/Views/Extend + * @example views/extend.php + */ +function elgg_extend_view($view, $view_extension, $priority = 501) { +	global $CONFIG; + +	if (!isset($CONFIG->views)) { +		$CONFIG->views = (object) array( +			'extensions' => array(), +		); +		$CONFIG->views->extensions[$view][500] = (string)$view; +	} else { +		if (!isset($CONFIG->views->extensions[$view])) { +			$CONFIG->views->extensions[$view][500] = (string)$view; +		} +	} + +	// raise priority until it doesn't match one already registered +	while (isset($CONFIG->views->extensions[$view][$priority])) { +		$priority++; +	} + +	$CONFIG->views->extensions[$view][$priority] = (string)$view_extension; +	ksort($CONFIG->views->extensions[$view]); +} + +/** + * Unextends a view. + * + * @param string $view           The view that was extended. + * @param string $view_extension This view that was added to $view + * + * @return bool + * @since 1.7.2 + */ +function elgg_unextend_view($view, $view_extension) { +	global $CONFIG; + +	if (!isset($CONFIG->views->extensions[$view])) { +		return FALSE; +	} + +	$priority = array_search($view_extension, $CONFIG->views->extensions[$view]); +	if ($priority === FALSE) { +		return FALSE; +	} + +	unset($CONFIG->views->extensions[$view][$priority]); + +	return TRUE; +} + +/** + * Assembles and outputs a full page. + * + * A "page" in Elgg is determined by the current view type and + * can be HTML for a browser, RSS for a feed reader, or + * Javascript, PHP and a number of other formats. + * + * @param string $title      Title + * @param string $body       Body + * @param string $page_shell Optional page shell to use. See page/shells view directory + * @param array  $vars       Optional vars array to pass to the page + *                           shell. Automatically adds title, body, and sysmessages + * + * @return string The contents of the page + * @since  1.8 + */ +function elgg_view_page($title, $body, $page_shell = 'default', $vars = array()) { + +	$messages = null; +	if (count_messages()) { +		// get messages - try for errors first +		$messages = system_messages(NULL, "error"); +		if (count($messages["error"]) == 0) { +			// no errors so grab rest of messages +			$messages = system_messages(null, ""); +		} else { +			// we have errors - clear out remaining messages +			system_messages(null, ""); +		} +	} + +	$vars['title'] = $title; +	$vars['body'] = $body; +	$vars['sysmessages'] = $messages; + +	$vars = elgg_trigger_plugin_hook('output:before', 'page', null, $vars); +	 +	// check for deprecated view +	if ($page_shell == 'default' && elgg_view_exists('pageshells/pageshell')) { +		elgg_deprecated_notice("pageshells/pageshell is deprecated by page/$page_shell", 1.8); +		$output = elgg_view('pageshells/pageshell', $vars); +	} else { +		$output = elgg_view("page/$page_shell", $vars); +	} + +	$vars['page_shell'] = $page_shell; + +	// Allow plugins to mod output +	return elgg_trigger_plugin_hook('output', 'page', $vars, $output); +} + +/** + * Displays a layout with optional parameters. + * + * Layouts provide consistent organization of pages and other blocks of content. + * There are a few default layouts in core: + *  - admin                   A special layout for the admin area. + *  - one_column              A single content column. + *  - one_sidebar             A content column with sidebar. + *  - two_sidebar             A content column with two sidebars. + *  - widgets                 A widget canvas. + * + * The layout views take the form page/layouts/$layout_name + * See the individual layouts for what options are supported. The three most + * common layouts have these parameters: + * one_column + *     content => string + * one_sidebar + *     content => string + *     sidebar => string (optional) + * content + *     content => string + *     sidebar => string (optional) + *     buttons => string (override the default add button) + *     title   => string (override the default title) + *     filter_context => string (selected content filter) + *     See the content layout view for more parameters + * + * @param string $layout_name The name of the view in page/layouts/. + * @param array  $vars        Associative array of parameters for the layout view + * + * @return string The layout + */ +function elgg_view_layout($layout_name, $vars = array()) { + +	if (is_string($vars) || $vars === null) { +		elgg_deprecated_notice("The use of unlimited optional string arguments in elgg_view_layout() was deprecated in favor of an options array", 1.8); +		$arg = 1; +		$param_array = array(); +		while ($arg < func_num_args()) { +			$param_array['area' . $arg] = func_get_arg($arg); +			$arg++; +		} +	} else { +		$param_array = $vars; +	} + +	$params = elgg_trigger_plugin_hook('output:before', 'layout', null, $param_array); + +	// check deprecated location +	if (elgg_view_exists("canvas/layouts/$layout_name")) { +		elgg_deprecated_notice("canvas/layouts/$layout_name is deprecated by page/layouts/$layout_name", 1.8); +		$output = elgg_view("canvas/layouts/$layout_name", $params); +	} elseif (elgg_view_exists("page/layouts/$layout_name")) { +		$output = elgg_view("page/layouts/$layout_name", $params); +	} else { +		$output = elgg_view("page/layouts/default", $params); +	} + +	return elgg_trigger_plugin_hook('output:after', 'layout', $params, $output); +} + +/** + * Render a menu + * + * @see elgg_register_menu_item() for documentation on adding menu items and + * navigation.php for information on the different menus available. + * + * This function triggers a 'register', 'menu:<menu name>' plugin hook that enables + * plugins to add menu items just before a menu is rendered. This is used by + * dynamic menus (menus that change based on some input such as the user hover + * menu). Using elgg_register_menu_item() in response to the hook can cause + * incorrect links to show up. See the blog plugin's blog_owner_block_menu() + * for an example of using this plugin hook. + * + * An additional hook is the 'prepare', 'menu:<menu name>' which enables plugins + * to modify the structure of the menu (sort it, remove items, set variables on + * the menu items). + * + * elgg_view_menu() uses views in navigation/menu + * + * @param string $menu_name The name of the menu + * @param array  $vars      An associative array of display options for the menu. + *                          Options include: + *                              sort_by => string or php callback + *                                  string options: 'name', 'priority', 'title' (default), + *                                  'register' (registration order) or a + *                                  php callback (a compare function for usort) + *                              handler: string the page handler to build action URLs + *                              entity: ElggEntity to use to build action URLs + *                              class: string the class for the entire menu. + *                              show_section_headers: bool show headers before menu sections. + * + * @return string + * @since 1.8.0 + */ +function elgg_view_menu($menu_name, array $vars = array()) { +	global $CONFIG; + +	$vars['name'] = $menu_name; + +	$sort_by = elgg_extract('sort_by', $vars, 'text'); + +	if (isset($CONFIG->menus[$menu_name])) { +		$menu = $CONFIG->menus[$menu_name]; +	} else { +		$menu = array(); +	} + +	// Give plugins a chance to add menu items just before creation. +	// This supports dynamic menus (example: user_hover). +	$menu = elgg_trigger_plugin_hook('register', "menu:$menu_name", $vars, $menu); + +	$builder = new ElggMenuBuilder($menu); +	$vars['menu'] = $builder->getMenu($sort_by); +	$vars['selected_item'] = $builder->getSelected(); + +	// Let plugins modify the menu +	$vars['menu'] = elgg_trigger_plugin_hook('prepare', "menu:$menu_name", $vars, $vars['menu']); + +	if (elgg_view_exists("navigation/menu/$menu_name")) { +		return elgg_view("navigation/menu/$menu_name", $vars); +	} else { +		return elgg_view("navigation/menu/default", $vars); +	} +} + +/** + * Returns a string of a rendered entity. + * + * Entity views are either determined by setting the view property on the entity + * or by having a view named after the entity $type/$subtype.  Entities that have + * neither a view property nor a defined $type/$subtype view will fall back to + * using the $type/default view. + * + * The entity view is called with the following in $vars: + *  - ElggEntity 'entity' The entity being viewed + * + * Other common view $vars paramters: + *  - bool 'full_view' Whether to show a full or condensed view. + * + * @tip This function can automatically appends annotations to entities if in full + * view and a handler is registered for the entity:annotate.  See https://github.com/Elgg/Elgg/issues/964 and + * {@link elgg_view_entity_annotations()}. + * + * @param ElggEntity $entity The entity to display + * @param array      $vars   Array of variables to pass to the entity view. + *                           In Elgg 1.7 and earlier it was the boolean $full_view + * @param boolean    $bypass If false, will not pass to a custom template handler. + *                           {@see set_template_handler()} + * @param boolean    $debug  Complain if views are missing + * + * @return string HTML to display or false + * @link http://docs.elgg.org/Views/Entity + * @link http://docs.elgg.org/Entities + * @todo The annotation hook might be better as a generic plugin hook to append content. + */ +function elgg_view_entity(ElggEntity $entity, $vars = array(), $bypass = true, $debug = false) { + +	// No point continuing if entity is null +	if (!$entity || !($entity instanceof ElggEntity)) { +		return false; +	} + +	global $autofeed; +	$autofeed = true; + +	$defaults = array( +		'full_view' => false, +	); + +	if (is_array($vars)) { +		$vars = array_merge($defaults, $vars); +	} else { +		elgg_deprecated_notice("Update your use of elgg_view_entity()", 1.8); +		$vars = array( +			'full_view' => $vars, +		); +	} + +	$vars['entity'] = $entity; + + +	// if this entity has a view defined, use it +	$view = $entity->view; +	if (is_string($view)) { +		return elgg_view($view, $vars, $bypass, $debug); +	} + +	$entity_type = $entity->getType(); + +	$subtype = $entity->getSubtype(); +	if (empty($subtype)) { +		$subtype = 'default'; +	} + +	$contents = ''; +	if (elgg_view_exists("$entity_type/$subtype")) { +		$contents = elgg_view("$entity_type/$subtype", $vars, $bypass, $debug); +	} +	if (empty($contents)) { +		$contents = elgg_view("$entity_type/default", $vars, $bypass, $debug); +	} + +	// Marcus Povey 20090616 : Speculative and low impact approach for fixing #964 +	if ($vars['full_view']) { +		$annotations = elgg_view_entity_annotations($entity, $vars['full_view']); + +		if ($annotations) { +			$contents .= $annotations; +		} +	} +	return $contents; +} + +/** + * View the icon of an entity + * + * Entity views are determined by having a view named after the entity $type/$subtype. + * Entities that do not have a defined icon/$type/$subtype view will fall back to using + * the icon/$type/default view. + * + * @param ElggEntity $entity The entity to display + * @param string     $size   The size: tiny, small, medium, large + * @param array      $vars   An array of variables to pass to the view. Some possible + *                           variables are img_class and link_class. See the + *                           specific icon view for more parameters. + * + * @return string HTML to display or false + */ +function elgg_view_entity_icon(ElggEntity $entity, $size = 'medium', $vars = array()) { + +	// No point continuing if entity is null +	if (!$entity || !($entity instanceof ElggEntity)) { +		return false; +	} + +	$vars['entity'] = $entity; +	$vars['size'] = $size; + +	$entity_type = $entity->getType(); + +	$subtype = $entity->getSubtype(); +	if (empty($subtype)) { +		$subtype = 'default'; +	} + +	$contents = ''; +	if (elgg_view_exists("icon/$entity_type/$subtype")) { +		$contents = elgg_view("icon/$entity_type/$subtype", $vars); +	} +	if (empty($contents)) { +		$contents = elgg_view("icon/$entity_type/default", $vars); +	} +	if (empty($contents)) { +		$contents = elgg_view("icon/default", $vars); +	} + +	return $contents; +} + +/** + * Returns a string of a rendered annotation. + * + * Annotation views are expected to be in annotation/$annotation_name. + * If a view is not found for $annotation_name, the default annotation/default + * will be used. + * + * @warning annotation/default is not currently defined in core. + * + * The annotation view is called with the following in $vars: + *  - ElggEntity 'annotation' The annotation being viewed. + * + * @param ElggAnnotation $annotation The annotation to display + * @param array          $vars       Variable array for view. + * @param bool           $bypass     If false, will not pass to a custom + *                                   template handler. {@see set_template_handler()} + * @param bool           $debug      Complain if views are missing + * + * @return string/false Rendered annotation + */ +function elgg_view_annotation(ElggAnnotation $annotation, array $vars = array(), $bypass = true, $debug = false) { +	global $autofeed; +	$autofeed = true; + +	$defaults = array( +		'full_view' => true, +	); + +	$vars = array_merge($defaults, $vars); +	$vars['annotation'] = $annotation; + +	// @todo setting the view on an annotation is not advertised anywhere +	// do we want to keep this? +	$view = $annotation->view; +	if (is_string($view)) { +		return elgg_view($view, $vars, $bypass, $debug); +	} + +	$name = $annotation->name; +	if (empty($name)) { +		return false; +	} + +	if (elgg_view_exists("annotation/$name")) { +		return elgg_view("annotation/$name", $vars, $bypass, $debug); +	} else { +		return elgg_view("annotation/default", $vars, $bypass, $debug); +	} +} + +/** + * Returns a rendered list of entities with pagination. This function should be + * called by wrapper functions. + * + * @see elgg_list_entities() + * @see list_user_friends_objects() + * @see elgg_list_entities_from_metadata() + * @see elgg_list_entities_from_relationships() + * @see elgg_list_entities_from_annotations() + * + * @param array $entities Array of entities + * @param array $vars     Display variables + *		'count'            The total number of entities across all pages + *		'offset'           The current indexing offset + *		'limit'            The number of entities to display per page + *		'full_view'        Display the full view of the entities? + *		'list_class'       CSS class applied to the list + *		'item_class'       CSS class applied to the list items + *		'pagination'       Display pagination? + *		'list_type'        List type: 'list' (default), 'gallery' + *		'list_type_toggle' Display the list type toggle? + * + * @return string The rendered list of entities + * @access private + */ +function elgg_view_entity_list($entities, $vars = array(), $offset = 0, $limit = 10, $full_view = true, +$list_type_toggle = true, $pagination = true) { + +	if (!$vars["limit"] && !$vars["offset"]) { +		// no need for pagination if listing is unlimited
 +		$vars["pagination"] = false;
 +	}
 +		 +	if (!is_int($offset)) { +		$offset = (int)get_input('offset', 0); +	} + +	// list type can be passed as request parameter +	$list_type = get_input('list_type', 'list'); +	if (get_input('listtype')) { +		elgg_deprecated_notice("'listtype' has been deprecated by 'list_type' for lists", 1.8); +		$list_type = get_input('listtype'); +	} + +	if (is_array($vars)) { +		// new function +		$defaults = array( +			'items' => $entities, +			'list_class' => 'elgg-list-entity', +			'full_view' => true, +			'pagination' => true, +			'list_type' => $list_type, +			'list_type_toggle' => false, +			'offset' => $offset, +		); + +		$vars = array_merge($defaults, $vars); + +	} else { +		// old function parameters +		elgg_deprecated_notice("Please update your use of elgg_view_entity_list()", 1.8); + +		$vars = array( +			'items' => $entities, +			'count' => (int) $vars, // the old count parameter +			'offset' => $offset, +			'limit' => (int) $limit, +			'full_view' => $full_view, +			'pagination' => $pagination, +			'list_type' => $list_type, +			'list_type_toggle' => $list_type_toggle, +			'list_class' => 'elgg-list-entity', +		); +	} + +	if ($vars['list_type'] != 'list') { +		return elgg_view('page/components/gallery', $vars); +	} else { +		return elgg_view('page/components/list', $vars); +	} +} + +/** + * Returns a rendered list of annotations, plus pagination. This function + * should be called by wrapper functions. + * + * @param array $annotations Array of annotations + * @param array $vars        Display variables + *		'count'      The total number of annotations across all pages + *		'offset'     The current indexing offset + *		'limit'      The number of annotations to display per page + *		'full_view'  Display the full view of the annotation? + *		'list_class' CSS Class applied to the list + *		'offset_key' The url parameter key used for offset + * + * @return string The list of annotations + * @access private + */ +function elgg_view_annotation_list($annotations, array $vars = array()) { +	$defaults = array( +		'items' => $annotations, +		'list_class' => 'elgg-list-annotation elgg-annotation-list', // @todo remove elgg-annotation-list in Elgg 1.9 +		'full_view' => true, +		'offset_key' => 'annoff', +	); +	 +	$vars = array_merge($defaults, $vars); +	 +	if (!$vars["limit"] && !$vars["offset"]) {
 +		// no need for pagination if listing is unlimited
 +		$vars["pagination"] = false;
 +	} + +	return elgg_view('page/components/list', $vars); +} + +/** + * Display a plugin-specified rendered list of annotations for an entity. + * + * This displays the output of functions registered to the entity:annotation, + * $entity_type plugin hook. + * + * This is called automatically by the framework from {@link elgg_view_entity()} + * + * @param ElggEntity $entity    Entity + * @param bool       $full_view Display full view? + * + * @return mixed string or false on failure + * @todo Change the hook name. + */ +function elgg_view_entity_annotations(ElggEntity $entity, $full_view = true) { +	if (!($entity instanceof ElggEntity)) { +		return false; +	} + +	$entity_type = $entity->getType(); + +	$annotations = elgg_trigger_plugin_hook('entity:annotate', $entity_type, +		array( +			'entity' => $entity, +			'full_view' => $full_view, +		) +	); + +	return $annotations; +} + +/** + * Renders a title. + * + * This is a shortcut for {@elgg_view page/elements/title}. + * + * @param string $title The page title + * @param array  $vars  View variables (was submenu be displayed? (deprecated)) + * + * @return string The HTML (etc) + */ +function elgg_view_title($title, $vars = array()) { +	if (!is_array($vars)) { +		elgg_deprecated_notice('setting $submenu in elgg_view_title() is deprecated', 1.8); +		$vars = array('submenu' => $vars); +	} + +	$vars['title'] = $title; + +	return elgg_view('page/elements/title', $vars); +} + +/** + * Displays a UNIX timestamp in a friendly way + * + * @see elgg_get_friendly_time() + * + * @param int $time A UNIX epoch timestamp + * + * @return string The friendly time HTML + * @since 1.7.2 + */ +function elgg_view_friendly_time($time) { +	return elgg_view('output/friendlytime', array('time' => $time)); +} + + +/** + * Returns rendered comments and a comment form for an entity. + * + * @tip Plugins can override the output by registering a handler + * for the comments, $entity_type hook.  The handler is responsible + * for formatting the comments and the add comment form. + * + * @param ElggEntity $entity      The entity to view comments of + * @param bool       $add_comment Include a form to add comments? + * @param array      $vars        Variables to pass to comment view + * + * @return string|false Rendered comments or false on failure + * @link http://docs.elgg.org/Entities/Comments + * @link http://docs.elgg.org/Annotations/Comments + */ +function elgg_view_comments($entity, $add_comment = true, array $vars = array()) { +	if (!($entity instanceof ElggEntity)) { +		return false; +	} + +	$vars['entity'] = $entity; +	$vars['show_add_form'] = $add_comment; +	$vars['class'] = elgg_extract('class', $vars, "{$entity->getSubtype()}-comments"); + +	$output = elgg_trigger_plugin_hook('comments', $entity->getType(), $vars, false); +	if ($output) { +		return $output; +	} else { +		return elgg_view('page/elements/comments', $vars); +	} +} + +/** + * Wrapper function for the image block display pattern. + * + * Fixed width media on the side (image, icon, flash, etc.). + * Descriptive content filling the rest of the column. + * + * This is a shortcut for {@elgg_view page/components/image_block}. + * + * @param string $image The icon and other information + * @param string $body  Description content + * @param array  $vars  Additional parameters for the view + * + * @return string + * @since 1.8.0 + */ +function elgg_view_image_block($image, $body, $vars = array()) { +	$vars['image'] = $image; +	$vars['body'] = $body; +	return elgg_view('page/components/image_block', $vars); +} + +/** + * Wrapper function for the module display pattern. + * + * Box with header, body, footer + * + * This is a shortcut for {@elgg_view page/components/module}. + * + * @param string $type  The type of module (main, info, popup, aside, etc.) + * @param string $title A title to put in the header + * @param string $body  Content of the module + * @param array  $vars  Additional parameters for the module + * + * @return string + * @since 1.8.0 + */ +function elgg_view_module($type, $title, $body, array $vars = array()) { +	$vars['class'] = elgg_extract('class', $vars, '') . " elgg-module-$type"; +	$vars['title'] = $title; +	$vars['body'] = $body; +	return elgg_view('page/components/module', $vars); +} + +/** + * Renders a human-readable representation of a river item + * + * @param ElggRiverItem $item A river item object + * @param array         $vars An array of variables for the view + * + * @return string returns empty string if could not be rendered + */ +function elgg_view_river_item($item, array $vars = array()) { +	if (!($item instanceof ElggRiverItem)) { +		return ''; +	} +	// checking default viewtype since some viewtypes do not have unique views per item (rss) +	$view = $item->getView(); +	if (!$view || !elgg_view_exists($view, 'default')) { +		return ''; +	} + +	$subject = $item->getSubjectEntity(); +	$object = $item->getObjectEntity(); +	if (!$subject || !$object) { +		// subject is disabled or subject/object deleted +		return ''; +	} + +	// @todo this needs to be cleaned up +	// Don't hide objects in closed groups that a user can see. +	// see https://github.com/elgg/elgg/issues/4789 +	//	else { +	//		// hide based on object's container +	//		$visibility = ElggGroupItemVisibility::factory($object->container_guid); +	//		if ($visibility->shouldHideItems) { +	//			return ''; +	//		} +	//	} + +	$vars['item'] = $item; + +	return elgg_view('river/item', $vars); +} + +/** + * Convenience function for generating a form from a view in a standard location. + * + * This function assumes that the body of the form is located at "forms/$action" and + * sets the action by default to "action/$action".  Automatically wraps the forms/$action + * view with a <form> tag and inserts the anti-csrf security tokens. + * + * @tip This automatically appends elgg-form-action-name to the form's class. It replaces any + * slashes with dashes (blog/save becomes elgg-form-blog-save) + * + * @example + * <code>echo elgg_view_form('login');</code> + * + * This would assume a "login" form body to be at "forms/login" and would set the action + * of the form to "http://yoursite.com/action/login". + * + * If elgg_view('forms/login') is: + * <input type="text" name="username" /> + * <input type="password" name="password" /> + * + * Then elgg_view_form('login') generates: + * <form action="http://yoursite.com/action/login" method="post"> + *     ...security tokens... + *     <input type="text" name="username" /> + *     <input type="password" name="password" /> + * </form> + * + * @param string $action    The name of the action. An action name does not include + *                          the leading "action/". For example, "login" is an action name. + * @param array  $form_vars $vars environment passed to the "input/form" view + * @param array  $body_vars $vars environment passed to the "forms/$action" view + * + * @return string The complete form + */ +function elgg_view_form($action, $form_vars = array(), $body_vars = array()) { +	global $CONFIG; + +	$defaults = array( +		'action' => $CONFIG->wwwroot . "action/$action", +		'body' => elgg_view("forms/$action", $body_vars) +	); + +	$form_class = 'elgg-form-' . preg_replace('/[^a-z0-9]/i', '-', $action); + +	// append elgg-form class to any class options set +	if (isset($form_vars['class'])) { +		$form_vars['class'] = $form_vars['class'] . " $form_class"; +	} else { +		$form_vars['class'] = $form_class; +	} + +	return elgg_view('input/form', array_merge($defaults, $form_vars)); +} + +/** + * View an item in a list + * + * @param ElggEntity|ElggAnnotation $item + * @param array  $vars Additional parameters for the rendering + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_view_list_item($item, array $vars = array()) { +	global $CONFIG; + +	$type = $item->getType(); +	if (in_array($type, $CONFIG->entity_types)) { +		return elgg_view_entity($item, $vars); +	} else if ($type == 'annotation') { +		return elgg_view_annotation($item, $vars); +	} else if ($type == 'river') { +		return elgg_view_river_item($item, $vars); +	} + +	return ''; +} + +/** + * View one of the elgg sprite icons + * + * Shorthand for <span class="elgg-icon elgg-icon-$name"></span> + * + * @param string $name  The specific icon to display + * @param string $class Additional class: float, float-alt, or custom class + * + * @return string The html for displaying an icon + */ +function elgg_view_icon($name, $class = '') { +	// @todo deprecate boolean in Elgg 1.9 +	if ($class === true) { +		$class = 'float'; +	} +	return "<span class=\"elgg-icon elgg-icon-$name $class\"></span>"; +} + +/** + * Displays a user's access collections, using the core/friends/collections view + * + * @param int $owner_guid The GUID of the owning user + * + * @return string A formatted rendition of the collections + * @todo Move to the friends/collection.php page. + * @access private + */ +function elgg_view_access_collections($owner_guid) { +	if ($collections = get_user_access_collections($owner_guid)) { +		foreach ($collections as $key => $collection) { +			$collections[$key]->members = get_members_of_access_collection($collection->id, true); +			$collections[$key]->entities = get_user_friends($owner_guid, "", 9999); +		} +	} + +	return elgg_view('core/friends/collections', array('collections' => $collections)); +} + +/** + * Registers a function to handle templates. + * + * Alternative template handlers can be registered to handle + * all output functions.  By default, {@link elgg_view()} will + * simply include the view file.  If an alternate template handler + * is registered, the view name and passed $vars will be passed to the + * registered function, which is then responsible for generating and returning + * output. + * + * Template handlers need to accept two arguments: string $view_name and array + * $vars. + * + * @warning This is experimental. + * + * @param string $function_name The name of the function to pass to. + * + * @return bool + * @see elgg_view() + * @link http://docs.elgg.org/Views/TemplateHandlers + */ +function set_template_handler($function_name) { +	global $CONFIG; + +	if (is_callable($function_name)) { +		$CONFIG->template_handler = $function_name; +		return true; +	} +	return false; +} + +/** + * Returns the name of views for in a directory. + * + * Use this to get all namespaced views under the first element. + * + * @param string $dir  The main directory that holds the views. (mod/profile/views/) + * @param string $base The root name of the view to use, without the viewtype. (profile) + * + * @return array + * @since 1.7.0 + * @todo Why isn't this used anywhere else but in elgg_view_tree()? + * Seems like a useful function for autodiscovery. + * @access private + */ +function elgg_get_views($dir, $base) { +	$return = array(); +	if (file_exists($dir) && is_dir($dir)) { +		if ($handle = opendir($dir)) { +			while ($view = readdir($handle)) { +				if (!in_array($view, array('.', '..', '.svn', 'CVS'))) { +					if (is_dir($dir . '/' . $view)) { +						if ($val = elgg_get_views($dir . '/' . $view, $base . '/' . $view)) { +							$return = array_merge($return, $val); +						} +					} else { +						$view = str_replace('.php', '', $view); +						$return[] = $base . '/' . $view; +					} +				} +			} +		} +	} + +	return $return; +} + +/** + * Returns all views below a partial view. + * + * Settings $view_root = 'profile' will show all available views under + * the "profile" namespace. + * + * @param string $view_root The root view + * @param string $viewtype  Optionally specify a view type + *                          other than the current one. + * + * @return array A list of view names underneath that root view + * @todo This is used once in the deprecated get_activity_stream_data() function. + * @access private + */ +function elgg_view_tree($view_root, $viewtype = "") { +	global $CONFIG; +	static $treecache = array(); + +	// Get viewtype +	if (!$viewtype) { +		$viewtype = elgg_get_viewtype(); +	} + +	// A little light internal caching +	if (!empty($treecache[$view_root])) { +		return $treecache[$view_root]; +	} + +	// Examine $CONFIG->views->locations +	if (isset($CONFIG->views->locations[$viewtype])) { +		foreach ($CONFIG->views->locations[$viewtype] as $view => $path) { +			$pos = strpos($view, $view_root); +			if ($pos === 0) { +				$treecache[$view_root][] = $view; +			} +		} +	} + +	// Now examine core +	$location = $CONFIG->viewpath; +	$viewtype = elgg_get_viewtype(); +	$root = $location . $viewtype . '/' . $view_root; + +	if (file_exists($root) && is_dir($root)) { +		$val = elgg_get_views($root, $view_root); +		if (!is_array($treecache[$view_root])) { +			$treecache[$view_root] = array(); +		} +		$treecache[$view_root] = array_merge($treecache[$view_root], $val); +	} + +	return $treecache[$view_root]; +} + +/** + * Auto-registers views from a location. + * + * @note Views in plugin/views/ are automatically registered for active plugins. + * Plugin authors would only need to call this if optionally including + * an entire views structure. + * + * @param string $view_base          Optional The base of the view name without the view type. + * @param string $folder             Required The folder to begin looking in + * @param string $base_location_path The base views directory to use with elgg_set_view_location() + * @param string $viewtype           The type of view we're looking at (default, rss, etc) + * + * @return bool returns false if folder can't be read + * @since 1.7.0 + * @see elgg_set_view_location() + * @todo This seems overly complicated. + * @access private + */ +function autoregister_views($view_base, $folder, $base_location_path, $viewtype) { +	if ($handle = opendir($folder)) { +		while ($view = readdir($handle)) { +			if (!in_array($view, array('.', '..', '.svn', 'CVS')) && !is_dir($folder . "/" . $view)) { +				// this includes png files because some icons are stored within view directories. +				// See commit [1705] +				if ((substr_count($view, ".php") > 0) || (substr_count($view, ".png") > 0)) { +					if (!empty($view_base)) { +						$view_base_new = $view_base . "/"; +					} else { +						$view_base_new = ""; +					} + +					elgg_set_view_location($view_base_new . str_replace('.php', '', $view), +						$base_location_path, $viewtype); +				} +			} else if (!in_array($view, array('.', '..', '.svn', 'CVS')) && is_dir($folder . "/" . $view)) { +				if (!empty($view_base)) { +					$view_base_new = $view_base . "/"; +				} else { +					$view_base_new = ""; +				} +				autoregister_views($view_base_new . $view, $folder . "/" . $view, +					$base_location_path, $viewtype); +			} +		} +		return TRUE; +	} + +	return FALSE; +} + +/** + * Add the rss link to the extras when if needed + * + * @return void + * @access private + */ +function elgg_views_add_rss_link() { +	global $autofeed; +	if (isset($autofeed) && $autofeed == true) { +		$url = current_page_url(); +		if (substr_count($url, '?')) { +			$url .= "&view=rss"; +		} else { +			$url .= "?view=rss"; +		} + +		$url = elgg_format_url($url); +		elgg_register_menu_item('extras', array( +			'name' => 'rss', +			'text' => elgg_view_icon('rss'), +			'href' => $url, +			'title' => elgg_echo('feed:rss'), +		)); +	} +} + +/** + * Registers deprecated views to avoid making some pages from older plugins + * completely empty. + * + * @access private + */ +function elgg_views_handle_deprecated_views() { +	$location = elgg_get_view_location('page_elements/contentwrapper'); +	if ($location === "/var/www/views/") { +		elgg_extend_view('page_elements/contentwrapper', 'page/elements/wrapper'); +	} +} + +/** + * Initialize viewtypes on system boot event + * This ensures simplecache is cleared during upgrades. See #2252 + * + * @return void + * @access private + * @elgg_event_handler boot system + */ +function elgg_views_boot() { +	global $CONFIG; + +	elgg_register_simplecache_view('css/ie'); +	elgg_register_simplecache_view('css/ie6'); +	elgg_register_simplecache_view('css/ie7'); + +	elgg_register_js('jquery', '/vendors/jquery/jquery-1.6.4.min.js', 'head'); +	elgg_register_js('jquery-ui', '/vendors/jquery/jquery-ui-1.8.16.min.js', 'head'); +	elgg_register_js('jquery.form', '/vendors/jquery/jquery.form.js'); + +	elgg_register_simplecache_view('js/elgg'); +	$elgg_js_url = elgg_get_simplecache_url('js', 'elgg'); +	elgg_register_js('elgg', $elgg_js_url, 'head'); + +	elgg_load_js('jquery'); +	elgg_load_js('jquery-ui'); +	elgg_load_js('elgg'); + +	elgg_register_simplecache_view('js/lightbox'); +	$lightbox_js_url = elgg_get_simplecache_url('js', 'lightbox'); +	elgg_register_js('lightbox', $lightbox_js_url); + +	elgg_register_simplecache_view('css/lightbox'); +	$lightbox_css_url = elgg_get_simplecache_url('css', 'lightbox'); +	elgg_register_css('lightbox', $lightbox_css_url); + +	elgg_register_simplecache_view('css/elgg'); +	$elgg_css_url = elgg_get_simplecache_url('css', 'elgg'); +	elgg_register_css('elgg', $elgg_css_url); + +	elgg_load_css('elgg'); + +	elgg_register_ajax_view('js/languages'); + +	elgg_register_plugin_hook_handler('output:before', 'layout', 'elgg_views_add_rss_link'); + +	// discover the built-in view types +	// @todo the cache is loaded in load_plugins() but we need to know view_types earlier +	$view_path = $CONFIG->viewpath; + +	$views = scandir($view_path); + +	foreach ($views as $view) { +		if ($view[0] !== '.' && is_dir($view_path . $view)) { +			elgg_register_viewtype($view); +		} +	} + +	// set default icon sizes - can be overridden in settings.php or with plugin +	if (!isset($CONFIG->icon_sizes)) { +		$icon_sizes = array( +			'topbar' => array('w' => 16, 'h' => 16, 'square' => TRUE, 'upscale' => TRUE), +			'tiny' => array('w' => 25, 'h' => 25, 'square' => TRUE, 'upscale' => TRUE), +			'small' => array('w' => 40, 'h' => 40, 'square' => TRUE, 'upscale' => TRUE), +			'medium' => array('w' => 100, 'h' => 100, 'square' => TRUE, 'upscale' => TRUE), +			'large' => array('w' => 200, 'h' => 200, 'square' => FALSE, 'upscale' => FALSE), +			'master' => array('w' => 550, 'h' => 550, 'square' => FALSE, 'upscale' => FALSE), +		); +		elgg_set_config('icon_sizes', $icon_sizes); +	} +} + +elgg_register_event_handler('boot', 'system', 'elgg_views_boot'); +elgg_register_event_handler('init', 'system', 'elgg_views_handle_deprecated_views'); diff --git a/engine/lib/web_services.php b/engine/lib/web_services.php new file mode 100644 index 000000000..51cad6f39 --- /dev/null +++ b/engine/lib/web_services.php @@ -0,0 +1,1454 @@ +<?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(), ErrorResult::$RESULT_FAIL_AUTHTOKEN); +		} +	} + +	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) { +	// check if username is an email address
 +	if (is_email_address($username)) {
 +		$users = get_user_by_email($username);
 +			
 +		// check if we have a unique user
 +		if (is_array($users) && (count($users) == 1)) {
 +			$username = $users[0]->username;
 +		}
 +	}
 +	
 +	// validate username and password +	if (true === elgg_authenticate($username, $password)) { +		$token = create_user_token($username); +		if ($token) { +			return $token; +		} +	} + +	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/json/?method=test +	$response_format = array_shift($request); +	// Which view - xml, json, ... +	if ($response_format && elgg_is_valid_view_type($response_format)) { +		elgg_set_viewtype($response_format); +	} else { +		// default to json +		elgg_set_viewtype("json"); +	} + +	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..86b3e8219 --- /dev/null +++ b/engine/lib/widgets.php @@ -0,0 +1,420 @@ +<?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 = is_array($context) ? $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 ElggEntity $entity The entity being created + * @return void + * @access private + */ +function elgg_create_default_widgets($event, $type, $entity) { +	$default_widget_info = elgg_get_config('default_widget_info'); + +	if (!$default_widget_info || !$entity) { +		return; +	} + +	$type = $entity->getType(); +	$subtype = $entity->getSubtype(); + +	// event is already guaranteed by the hook registration. +	// need to check subtype and type. +	foreach ($default_widget_info as $info) { +		if ($info['entity_type'] == $type) { +			if ($info['entity_subtype'] == ELGG_ENTITIES_ANY_VALUE || $info['entity_subtype'] == $subtype) { + +				// need to be able to access everything +				$old_ia = elgg_set_ignore_access(true); +				elgg_push_context('create_default_widgets'); + +				// pull in by widget context with widget owners as the site +				// not using elgg_get_widgets() because it sorts by columns and we don't care right now. +				$options = array( +					'type' => 'object', +					'subtype' => 'widget', +					'owner_guid' => elgg_get_site_entity()->guid, +					'private_setting_name' => 'context', +					'private_setting_value' => $info['widget_context'], +					'limit' => 0 +				); + +				$widgets = elgg_get_entities_from_private_settings($options); +				/* @var ElggWidget[] $widgets */ + +				foreach ($widgets as $widget) { +					// change the container and owner +					$new_widget = clone $widget; +					$new_widget->container_guid = $entity->guid; +					$new_widget->owner_guid = $entity->guid; + +					// pull in settings +					$settings = get_all_private_settings($widget->guid); + +					foreach ($settings as $name => $value) { +						$new_widget->$name = $value; +					} + +					$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..497459d83 --- /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 ElggXMLElement + */ +function xml_to_object($xml) { +	return new ElggXMLElement($xml); +}  | 
