diff options
Diffstat (limited to 'engine/lib')
| -rw-r--r-- | engine/lib/access.php | 158 | ||||
| -rw-r--r-- | engine/lib/admin.php | 6 | ||||
| -rw-r--r-- | engine/lib/annotations.php | 11 | ||||
| -rw-r--r-- | engine/lib/configuration.php | 26 | ||||
| -rw-r--r-- | engine/lib/elgglib.php | 58 | ||||
| -rw-r--r-- | engine/lib/entities.php | 385 | ||||
| -rw-r--r-- | engine/lib/extender.php | 9 | ||||
| -rw-r--r-- | engine/lib/group.php | 1 | ||||
| -rw-r--r-- | engine/lib/metadata.php | 122 | ||||
| -rw-r--r-- | engine/lib/navigation.php | 26 | ||||
| -rw-r--r-- | engine/lib/notification.php | 7 | ||||
| -rw-r--r-- | engine/lib/objects.php | 1 | ||||
| -rw-r--r-- | engine/lib/output.php | 134 | ||||
| -rw-r--r-- | engine/lib/pagehandler.php | 10 | ||||
| -rw-r--r-- | engine/lib/pageowner.php | 21 | ||||
| -rw-r--r-- | engine/lib/pam.php | 7 | ||||
| -rw-r--r-- | engine/lib/plugins.php | 22 | ||||
| -rw-r--r-- | engine/lib/relationships.php | 2 | ||||
| -rw-r--r-- | engine/lib/river.php | 48 | ||||
| -rw-r--r-- | engine/lib/sites.php | 13 | ||||
| -rw-r--r-- | engine/lib/upgrade.php | 55 | ||||
| -rw-r--r-- | engine/lib/upgrades/2010052601.php | 12 | ||||
| -rw-r--r-- | engine/lib/users.php | 1 | ||||
| -rw-r--r-- | engine/lib/views.php | 150 | ||||
| -rw-r--r-- | engine/lib/web_services.php | 44 | ||||
| -rw-r--r-- | engine/lib/xml.php | 38 | 
26 files changed, 907 insertions, 460 deletions
diff --git a/engine/lib/access.php b/engine/lib/access.php index e8b3b0d52..f7d3bf7ea 100644 --- a/engine/lib/access.php +++ b/engine/lib/access.php @@ -12,6 +12,26 @@   */  /** + * 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 @@ -29,10 +49,10 @@   */  function get_access_list($user_id = 0, $site_id = 0, $flush = false) {  	global $CONFIG, $init_finished; -	static $access_list; - -	if (!isset($access_list)) { -		$access_list = array(); +	$cache = _elgg_get_access_cache(); +	 +	if ($flush) { +		$cache->clear();  	}  	if ($user_id == 0) { @@ -45,20 +65,20 @@ function get_access_list($user_id = 0, $site_id = 0, $flush = false) {  	$user_id = (int) $user_id;  	$site_id = (int) $site_id; -	if (isset($access_list[$user_id]) && $flush == false) { -		return $access_list[$user_id]; -	} +	$hash = $user_id . $site_id . 'get_access_list'; -	$access = "(" . implode(",", get_access_array($user_id, $site_id, $flush)) . ")"; +	if ($cache[$hash]) { +		return $cache[$hash]; +	} +	 +	$access_array = get_access_array($user_id, $site_id, $flush); +	$access = "(" . implode(",", $access_array) . ")"; -	// only cache if done with init and access is enabled (unless admin user) -	// session is loaded before init is finished, so don't need to check for user session -	if ($init_finished && (elgg_is_admin_logged_in() || !elgg_get_ignore_access())) { -		$access_list[$user_id] = $access; -		return $access_list[$user_id]; -	} else { -		return $access; +	if ($init_finished) { +		$cache[$hash] = $access;  	} +	 +	return $access;  }  /** @@ -86,12 +106,10 @@ function get_access_list($user_id = 0, $site_id = 0, $flush = false) {  function get_access_array($user_id = 0, $site_id = 0, $flush = false) {  	global $CONFIG, $init_finished; -	// @todo everything from the db is cached. -	// this cache might be redundant. But db cache is flushed on every db write. -	static $access_array; +	$cache = _elgg_get_access_cache(); -	if (!isset($access_array)) { -		$access_array = array(); +	if ($flush) { +		$cache->clear();  	}  	if ($user_id == 0) { @@ -105,35 +123,41 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) {  	$user_id = (int) $user_id;  	$site_id = (int) $site_id; -	if (empty($access_array[$user_id]) || $flush == true) { -		$tmp_access_array = array(ACCESS_PUBLIC); +	$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()) { -			$tmp_access_array[] = ACCESS_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)"; +				. " WHERE am.user_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)"; -			if ($collections = get_data($query)) { +			$collections = get_data($query); +			if ($collections) {  				foreach ($collections as $collection) {  					if (!empty($collection->access_collection_id)) { -						$tmp_access_array[] = (int)$collection->access_collection_id; +						$access_array[] = (int)$collection->access_collection_id;  					}  				}  			}  			// Get ACLs owned.  			$query = "SELECT ag.id FROM {$CONFIG->dbprefix}access_collections ag "; -			$query .= "WHERE ag.owner_guid = {$user_id} AND (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; +			$query .= "WHERE ag.owner_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)"; -			if ($collections = get_data($query)) { +			$collections = get_data($query); +			if ($collections) {  				foreach ($collections as $collection) {  					if (!empty($collection->id)) { -						$tmp_access_array[] = (int)$collection->id; +						$access_array[] = (int)$collection->id;  					}  				}  			} @@ -141,21 +165,21 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) {  			$ignore_access = elgg_check_access_overrides($user_id);  			if ($ignore_access == true) { -				$tmp_access_array[] = ACCESS_PRIVATE; +				$access_array[] = ACCESS_PRIVATE;  			} +		} -			// only cache if done with init and access is enabled (unless admin user) -			// session is loaded before init is finished, so don't need to check for user session -			if ($init_finished && (elgg_is_admin_logged_in() || !elgg_get_ignore_access())) { -				$access_array[$user_id] = $tmp_access_array; -			} +		if ($init_finished) { +			$cache[$hash] = $access_array;  		} -	} else { -		$tmp_access_array = $access_array[$user_id];  	} -	$options = array('user_id' => $user_id, 'site_id' => $site_id); -	return elgg_trigger_plugin_hook('access:collections:read', 'user', $options, $tmp_access_array); +	$options = array( +		'user_id' => $user_id, +		'site_id' => $site_id +	); +	 +	return elgg_trigger_plugin_hook('access:collections:read', 'user', $options, $access_array);  }  /** @@ -401,9 +425,12 @@ function has_access_to_entity($entity, $user = null) {   * @link http://docs.elgg.org/Access   */  function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) { -	global $CONFIG; -	//@todo this is probably not needed since caching happens at the DB level. -	static $access_array; +	global $CONFIG, $init_finished; +	$cache = _elgg_get_access_cache(); + +	if ($flush) { +		$cache->clear(); +	}  	if ($user_id == 0) {  		$user_id = elgg_get_logged_in_user_guid(); @@ -416,37 +443,41 @@ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) {  	$user_id = (int) $user_id;  	$site_id = (int) $site_id; -	if (empty($access_array[$user_id]) || $flush == true) { -		$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})"; -		// ACCESS_PRIVATE through ACCESS_PUBLIC take 0 through 2 -		// @todo this AND clause is unnecessary because of id starts at 3 for table -		$query .= " AND ag.id >= 3"; +	$hash = $user_id . $site_id . 'get_write_access_array'; -		$tmp_access_array = 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) { -				$tmp_access_array[$collection->id] = $collection->name; +				$access_array[$collection->id] = $collection->name;  			}  		} -		$access_array[$user_id] = $tmp_access_array; -	} else { -		$tmp_access_array = $access_array[$user_id]; +		if ($init_finished) { +			$cache[$hash] = $access_array; +		}  	} -	$options = array('user_id' => $user_id, 'site_id' => $site_id); -	$tmp_access_array = elgg_trigger_plugin_hook('access:collections:write', 'user', -		$options, $tmp_access_array); - -	return $tmp_access_array; +	$options = array( +		'user_id' => $user_id, +		'site_id' => $site_id +	); +	return elgg_trigger_plugin_hook('access:collections:write', 'user', +		$options, $access_array);  }  /** @@ -476,7 +507,7 @@ function can_edit_access_collection($collection_id, $user_guid = null) {  		return false;  	} -	$write_access = get_write_access_array($user->getGUID(), null, true); +	$write_access = get_write_access_array($user->getGUID(), 0, true);  	// don't ignore access when checking users.  	if ($user_guid) { @@ -560,8 +591,6 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) {   * @see remove_user_from_access_collection()   */  function update_access_collection($collection_id, $members) { -	global $CONFIG; -  	$acl = get_access_collection($collection_id);  	if (!$acl) { @@ -877,6 +906,8 @@ function get_readable_access_level($entity_access_id) {   * @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.   * @@ -888,6 +919,8 @@ function get_readable_access_level($entity_access_id) {   * @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);  } @@ -1018,6 +1051,7 @@ function elgg_override_permissions($hook, $type, $value, $params) {   */  function access_test($hook, $type, $value, $params) {  	global $CONFIG; +  	$value[] = $CONFIG->path . 'engine/tests/api/access_collections.php';  	return $value;  } diff --git a/engine/lib/admin.php b/engine/lib/admin.php index b65d98c95..cb9524f11 100644 --- a/engine/lib/admin.php +++ b/engine/lib/admin.php @@ -233,6 +233,7 @@ function admin_init() {  	elgg_register_action('admin/site/update_basic', '', 'admin');  	elgg_register_action('admin/site/update_advanced', '', 'admin');  	elgg_register_action('admin/site/flush_cache', '', 'admin'); +	elgg_register_action('admin/site/unlock_upgrade', '', 'admin');  	elgg_register_action('admin/menu/save', '', 'admin'); @@ -268,8 +269,9 @@ function admin_init() {  	// 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', 'newest', 'users', 20); -	elgg_register_admin_menu_item('administer', 'add', 'users', 30); +	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 diff --git a/engine/lib/annotations.php b/engine/lib/annotations.php index 2036ccd61..3b9f84703 100644 --- a/engine/lib/annotations.php +++ b/engine/lib/annotations.php @@ -316,8 +316,6 @@ function elgg_list_annotations($options) {   *   *  annotation_owner_guids => NULL|ARR guids for annotaiton owners   * - *  annotation_ids => NULL|ARR Annotation IDs - *   * @return mixed If count, int. If not count, array. false on errors.   * @since 1.7.0   */ @@ -336,8 +334,6 @@ function elgg_get_entities_from_annotations(array $options = array()) {  		'annotation_owner_guids'				=>	ELGG_ENTITIES_ANY_VALUE, -		'annotation_ids'						=>	ELGG_ENTITIES_ANY_VALUE, -  		'order_by'								=>	'maxtime desc',  		'group_by'								=>	'a.entity_guid'  	); @@ -345,12 +341,13 @@ function elgg_get_entities_from_annotations(array $options = array()) {  	$options = array_merge($defaults, $options);  	$singulars = array('annotation_name', 'annotation_value', -	'annotation_name_value_pair', 'annotation_owner_guid', 'annotation_id'); +	'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 = elgg_entities_get_metastrings_options('annotation', $options)) { -		return FALSE; +	if (!$options) { +		return false;  	}  	// special sorting for annotations diff --git a/engine/lib/configuration.php b/engine/lib/configuration.php index 305aa00b6..b10e51130 100644 --- a/engine/lib/configuration.php +++ b/engine/lib/configuration.php @@ -91,23 +91,29 @@ function elgg_get_config($name, $site_guid = 0) {  		return $CONFIG->$name;  	} -	if ($site_guid === NULL) { +	if ($site_guid === null) {  		// installation wide setting  		$value = datalist_get($name);  	} else { -		// site specific setting -		if ($site_guid == 0) { -			$site_guid = (int) $CONFIG->site_id; +		// 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;  		} -		$value = get_config($name, $site_guid);  	} -	if ($value !== false) { -		$CONFIG->$name = $value; -		return $value; +	// @todo document why we don't cache false +	if ($value === false) { +		return null;  	} -	return null; +	$CONFIG->$name = $value; +	return $value;  }  /** @@ -558,6 +564,8 @@ function _elgg_load_site_config() {  	$CONFIG->url = $CONFIG->wwwroot;  	get_all_config(); +	// gives hint to elgg_get_config function how to approach missing values +	$CONFIG->site_config_loaded = true;  }  /** diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index 554b0561f..540605876 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -671,7 +671,7 @@ function elgg_register_event_handler($event, $object_type, $callback, $priority  	global $CONFIG;  	if (empty($event) || empty($object_type)) { -		return FALSE; +		return false;  	}  	if (!isset($CONFIG->events)) { @@ -684,8 +684,8 @@ function elgg_register_event_handler($event, $object_type, $callback, $priority  		$CONFIG->events[$event][$object_type] = array();  	} -	if (!is_callable($callback)) { -		return FALSE; +	if (!is_callable($callback, true)) { +		return false;  	}  	$priority = max((int) $priority, 0); @@ -695,7 +695,7 @@ function elgg_register_event_handler($event, $object_type, $callback, $priority  	}  	$CONFIG->events[$event][$object_type][$priority] = $callback;  	ksort($CONFIG->events[$event][$object_type]); -	return TRUE; +	return true;  }  /** @@ -710,9 +710,12 @@ function elgg_register_event_handler($event, $object_type, $callback, $priority   */  function elgg_unregister_event_handler($event, $object_type, $callback) {  	global $CONFIG; -	foreach ($CONFIG->events[$event][$object_type] as $key => $event_callback) { -		if ($event_callback == $callback) { -			unset($CONFIG->events[$event][$object_type][$key]); + +	if (isset($CONFIG->events[$event]) && isset($CONFIG->events[$event][$object_type])) { +		foreach ($CONFIG->events[$event][$object_type] as $key => $event_callback) { +			if ($event_callback == $callback) { +				unset($CONFIG->events[$event][$object_type][$key]); +			}  		}  	}  } @@ -770,14 +773,14 @@ function elgg_trigger_event($event, $object_type, $object = null) {  	foreach ($events as $callback_list) {  		if (is_array($callback_list)) {  			foreach ($callback_list as $callback) { -				if (call_user_func_array($callback, $args) === FALSE) { -					return FALSE; +				if (is_callable($callback) && (call_user_func_array($callback, $args) === false)) { +					return false;  				}  			}  		}  	} -	return TRUE; +	return true;  }  /** @@ -850,7 +853,7 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority =  	global $CONFIG;  	if (empty($hook) || empty($type)) { -		return FALSE; +		return false;  	}  	if (!isset($CONFIG->hooks)) { @@ -863,8 +866,8 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority =  		$CONFIG->hooks[$hook][$type] = array();  	} -	if (!is_callable($callback)) { -		return FALSE; +	if (!is_callable($callback, true)) { +		return false;  	}  	$priority = max((int) $priority, 0); @@ -874,7 +877,7 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority =  	}  	$CONFIG->hooks[$hook][$type][$priority] = $callback;  	ksort($CONFIG->hooks[$hook][$type]); -	return TRUE; +	return true;  }  /** @@ -889,9 +892,12 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority =   */  function elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback) {  	global $CONFIG; -	foreach ($CONFIG->hooks[$hook][$entity_type] as $key => $hook_callback) { -		if ($hook_callback == $callback) { -			unset($CONFIG->hooks[$hook][$entity_type][$key]); + +	if (isset($CONFIG->hooks[$hook]) && isset($CONFIG->hooks[$hook][$entity_type])) { +		foreach ($CONFIG->hooks[$hook][$entity_type] as $key => $hook_callback) { +			if ($hook_callback == $callback) { +				unset($CONFIG->hooks[$hook][$entity_type][$key]); +			}  		}  	}  } @@ -970,10 +976,12 @@ function elgg_trigger_plugin_hook($hook, $type, $params = null, $returnvalue = n  	foreach ($hooks as $callback_list) {  		if (is_array($callback_list)) {  			foreach ($callback_list as $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; +				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; +					}  				}  			}  		} @@ -1070,7 +1078,11 @@ function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) {  		case E_WARNING :  		case E_USER_WARNING :  		case E_RECOVERABLE_ERROR: // (e.g. type hint violation) -			error_log("PHP WARNING: $error"); +			 +			// check if the error wasn't suppressed by the error control operator (@) +			if (error_reporting()) { +				error_log("PHP WARNING: $error"); +			}  			break;  		default: @@ -1879,7 +1891,7 @@ function elgg_cacheable_view_page_handler($page, $type) {  		header("Content-type: $content_type");  		// @todo should js be cached when simple cache turned off -		//header('Expires: ' . date('r', time() + 864000)); +		//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)); diff --git a/engine/lib/entities.php b/engine/lib/entities.php index 3896cd58f..ce736ce05 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -17,13 +17,13 @@ global $ENTITY_CACHE;  $ENTITY_CACHE = array();  /** - * Cache subtypes and related class names once loaded. + * Cache subtypes and related class names.   * - * @global array $SUBTYPE_CACHE + * @global array|null $SUBTYPE_CACHE array once populated from DB, initially null   * @access private   */  global $SUBTYPE_CACHE; -$SUBTYPE_CACHE = NULL; +$SUBTYPE_CACHE = null;  /**   * Invalidate this class's entry in the cache. @@ -39,6 +39,8 @@ function invalidate_cache_for_entity($guid) {  	$guid = (int)$guid;  	unset($ENTITY_CACHE[$guid]); + +	elgg_get_metadata_cache()->clear($guid);  }  /** @@ -57,16 +59,24 @@ function invalidate_cache_for_entity($guid) {  function cache_entity(ElggEntity $entity) {  	global $ENTITY_CACHE; -	// Don't cache entities while access control is off, otherwise they could be +	// 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 (elgg_get_ignore_access()) { +	if (!($entity instanceof ElggPlugin) && elgg_get_ignore_access()) {  		return;  	}  	// Don't store too many or we'll have memory problems  	// TODO(evan): Pick a less arbitrary limit  	if (count($ENTITY_CACHE) > 256) { -		unset($ENTITY_CACHE[array_rand($ENTITY_CACHE)]); +		$random_guid = array_rand($ENTITY_CACHE); + +		unset($ENTITY_CACHE[$random_guid]); + +		// Purge separate metadata cache. Original idea was to do in entity destructor, but that would +		// have caused a bunch of unnecessary purges at every shutdown. Doing it this way we have no way +		// to know that the expunged entity will be GCed (might be another reference living), but that's +		// OK; the metadata will reload if necessary. +		elgg_get_metadata_cache()->clear($random_guid);  	}  	$ENTITY_CACHE[$entity->guid] = $entity; @@ -85,8 +95,6 @@ function cache_entity(ElggEntity $entity) {  function retrieve_cached_entity($guid) {  	global $ENTITY_CACHE; -	$guid = (int)$guid; -  	if (isset($ENTITY_CACHE[$guid])) {  		if ($ENTITY_CACHE[$guid]->isFullyLoaded()) {  			return $ENTITY_CACHE[$guid]; @@ -146,29 +154,23 @@ function retrieve_cached_entity_row($guid) {   * @access private   */  function get_subtype_id($type, $subtype) { -	global $CONFIG, $SUBTYPE_CACHE; - -	$type = sanitise_string($type); -	$subtype = sanitise_string($subtype); +	global $SUBTYPE_CACHE; -	if ($subtype == "") { -		return FALSE; +	if (!$subtype) { +		return false;  	} -	// @todo use the cache before hitting database -	$result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes -		where type='$type' and subtype='$subtype'"); - -	if ($result) { -		if (!$SUBTYPE_CACHE) { -			$SUBTYPE_CACHE = array(); -		} +	if ($SUBTYPE_CACHE === null) { +		_elgg_populate_subtype_cache(); +	} -		$SUBTYPE_CACHE[$result->id] = $result; +	// use the cache before hitting database +	$result = _elgg_retrieve_cached_subtype($type, $subtype); +	if ($result !== null) {  		return $result->id;  	} -	return FALSE; +	return false;  }  /** @@ -176,35 +178,67 @@ function get_subtype_id($type, $subtype) {   *   * @param int $subtype_id Subtype ID   * - * @return string Subtype name + * @return string|false Subtype name, false if subtype not found   * @link http://docs.elgg.org/DataModel/Entities/Subtypes   * @see get_subtype_from_id()   * @access private   */  function get_subtype_from_id($subtype_id) { -	global $CONFIG, $SUBTYPE_CACHE; - -	$subtype_id = (int)$subtype_id; +	global $SUBTYPE_CACHE;  	if (!$subtype_id) {  		return false;  	} +	if ($SUBTYPE_CACHE === null) { +		_elgg_populate_subtype_cache(); +	} +  	if (isset($SUBTYPE_CACHE[$subtype_id])) {  		return $SUBTYPE_CACHE[$subtype_id]->subtype;  	} -	$result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where id=$subtype_id"); -	if ($result) { -		if (!$SUBTYPE_CACHE) { -			$SUBTYPE_CACHE = array(); -		} +	return false; +} + +/** + * Retrieve subtype from the cache. + * + * @param string $type + * @param string $subtype + * @return stdClass|null + * + * @access private + */ +function _elgg_retrieve_cached_subtype($type, $subtype) { +	global $SUBTYPE_CACHE; -		$SUBTYPE_CACHE[$subtype_id] = $result; -		return $result->subtype; +	if ($SUBTYPE_CACHE === null) { +		_elgg_populate_subtype_cache();  	} -	return false; +	foreach ($SUBTYPE_CACHE as $obj) { +		if ($obj->type === $type && $obj->subtype === $subtype) { +			return $obj; +		} +	} +	return null; +} + +/** + * 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; +	}  }  /** @@ -223,25 +257,19 @@ function get_subtype_from_id($subtype_id) {   * @access private   */  function get_subtype_class($type, $subtype) { -	global $CONFIG, $SUBTYPE_CACHE; - -	$type = sanitise_string($type); -	$subtype = sanitise_string($subtype); +	global $SUBTYPE_CACHE; -	// @todo use the cache before going to the database -	$result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes -		where type='$type' and subtype='$subtype'"); - -	if ($result) { -		if (!$SUBTYPE_CACHE) { -			$SUBTYPE_CACHE = array(); -		} - -		$SUBTYPE_CACHE[$result->id] = $result; -		return $result->class; +	if ($SUBTYPE_CACHE === null) { +		_elgg_populate_subtype_cache(); +	} +	 +	// use the cache before going to the database +	$obj = _elgg_retrieve_cached_subtype($type, $subtype); +	if ($obj) { +		return $obj->class;  	} -	return NULL; +	return null;  }  /** @@ -255,29 +283,21 @@ function get_subtype_class($type, $subtype) {   * @access private   */  function get_subtype_class_from_id($subtype_id) { -	global $CONFIG, $SUBTYPE_CACHE; - -	$subtype_id = (int)$subtype_id; +	global $SUBTYPE_CACHE;  	if (!$subtype_id) { -		return false; +		return null;  	} +	if ($SUBTYPE_CACHE === null) { +		_elgg_populate_subtype_cache(); +	} +	  	if (isset($SUBTYPE_CACHE[$subtype_id])) {  		return $SUBTYPE_CACHE[$subtype_id]->class;  	} -	$result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where id=$subtype_id"); - -	if ($result) { -		if (!$SUBTYPE_CACHE) { -			$SUBTYPE_CACHE = array(); -		} -		$SUBTYPE_CACHE[$subtype_id] = $result; -		return $result->class; -	} - -	return NULL; +	return null;  }  /** @@ -303,21 +323,32 @@ function get_subtype_class_from_id($subtype_id) {   * @see get_entity()   */  function add_subtype($type, $subtype, $class = "") { -	global $CONFIG; -	$type = sanitise_string($type); -	$subtype = sanitise_string($subtype); -	$class = sanitise_string($class); +	global $CONFIG, $SUBTYPE_CACHE; -	// Short circuit if no subtype is given -	if ($subtype == "") { +	if (!$subtype) {  		return 0;  	}  	$id = get_subtype_id($type, $subtype); -	if ($id == 0) { -		return insert_data("insert into {$CONFIG->dbprefix}entity_subtypes" -			. " (type, subtype, class) values ('$type','$subtype','$class')"); +	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; @@ -359,22 +390,31 @@ function remove_subtype($type, $subtype) {  function update_subtype($type, $subtype, $class = '') {  	global $CONFIG, $SUBTYPE_CACHE; -	if (!$id = get_subtype_id($type, $subtype)) { -		return FALSE; +	$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); - -	$result = update_data("UPDATE {$CONFIG->dbprefix}entity_subtypes +	$class = sanitise_string($class); +	 +	$success = update_data("UPDATE {$CONFIG->dbprefix}entity_subtypes  		SET type = '$type', subtype = '$subtype', class = '$class'  		WHERE id = $id  	"); -	if ($result && isset($SUBTYPE_CACHE[$id])) { -		$SUBTYPE_CACHE[$id]->class = $class; +	if ($success && isset($SUBTYPE_CACHE[$id])) { +		$SUBTYPE_CACHE[$id]->class = $unescaped_class;  	} -	return $result; +	return $success;  }  /** @@ -606,12 +646,14 @@ function get_entity_as_row($guid) {   *   * @param stdClass $row The row of the entry in the entities table.   * - * @return object|false + * @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)) { @@ -698,7 +740,7 @@ function get_entity($guid) {  	// 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; +		return false;  	}  	// Check local cache first @@ -715,14 +757,29 @@ function get_entity($guid) {  			$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) { -		$new_entity = $shared_cache->load($guid); -		if ($new_entity) { -			return $new_entity; +		$cached_entity = $shared_cache->load($guid); +		// @todo store ACLs in memcache http://trac.elgg.org/ticket/3018#comment:3 +		if ($cached_entity) { +			// @todo use ACL and cached entity access_id to determine if user can see it +			return $cached_entity;  		}  	} -	$new_entity = entity_row_to_elggstar(get_entity_as_row($guid)); +	// don't let incomplete entities cause fatal exceptions +	try { +		$new_entity = entity_row_to_elggstar($entity_row); +	} catch (IncompleteEntityException $e) { +		return false; +	} +  	if ($new_entity) {  		cache_entity($new_entity);  	} @@ -967,19 +1024,32 @@ function elgg_get_entities(array $options = array()) {  			$query .= " LIMIT $offset, $limit";  		} -		$dt = get_data($query, $options['callback']); +		if ($options['callback'] === 'entity_row_to_elggstar') { +			$dt = _elgg_fetch_entities_from_sql($query); +		} else { +			$dt = get_data($query, $options['callback']); +		} +  		if ($dt) { -			foreach ($dt as $entity) { -				// If a custom callback is provided, it could return something other than ElggEntity, -				// so we have to do an explicit check here. -				if ($entity instanceof ElggEntity) { -					cache_entity($entity); +			// populate entity and metadata caches +			$guids = array(); +			foreach ($dt as $item) { +				// A custom callback could result in items that aren't ElggEntity's, so check for them +				if ($item instanceof ElggEntity) { +					cache_entity($item); +					// plugins usually have only settings +					if (!$item instanceof ElggPlugin) { +						$guids[] = $item->guid; +					}  				}  			}  			// @todo Without this, recursive delete fails. See #4568  			reset($dt); -		} +			if ($guids) { +				elgg_get_metadata_cache()->populateFromEntities($guids); +			} +		}  		return $dt;  	} else {  		$total = get_data_row($query); @@ -988,6 +1058,97 @@ function elgg_get_entities(array $options = array()) {  }  /** + * Return entities from an SQL query generated by elgg_get_entities. + * + * @param string $sql + * @return ElggEntity[] + * + * @access private + * @throws LogicException + */ +function _elgg_fetch_entities_from_sql($sql) { +	static $plugin_subtype; +	if (null === $plugin_subtype) { +		$plugin_subtype = get_subtype_id('object', 'plugin'); +	} + +	// Keys are types, values are columns that, if present, suggest that the secondary +	// table is already JOINed +	$types_to_optimize = array( +		'object' => 'title', +		'user' => 'password', +		'group' => 'name', +	); + +	$rows = get_data($sql); + +	// guids to look up in each type +	$lookup_types = array(); +	// maps GUIDs to the $rows key +	$guid_to_key = array(); + +	if (isset($rows[0]->type, $rows[0]->subtype) +			&& $rows[0]->type === 'object' +			&& $rows[0]->subtype == $plugin_subtype) { +		// Likely the entire resultset is plugins, which have already been optimized +		// to JOIN the secondary table. In this case we allow retrieving from cache, +		// but abandon the extra queries. +		$types_to_optimize = array(); +	} + +	// First pass: use cache where possible, gather GUIDs that we're optimizing +	foreach ($rows as $i => $row) { +		if (empty($row->guid) || empty($row->type)) { +			throw new LogicException('Entity row missing guid or type'); +		} +		if ($entity = retrieve_cached_entity($row->guid)) { +			$rows[$i] = $entity; +			continue; +		} +		if (isset($types_to_optimize[$row->type])) { +			// check if row already looks JOINed. +			if (isset($row->{$types_to_optimize[$row->type]})) { +				// Row probably already contains JOINed secondary table. Don't make another query just +				// to pull data that's already there +				continue; +			} +			$lookup_types[$row->type][] = $row->guid; +			$guid_to_key[$row->guid] = $i; +		} +	} +	// Do secondary queries and merge rows +	if ($lookup_types) { +		$dbprefix = elgg_get_config('dbprefix'); +	} +	foreach ($lookup_types as $type => $guids) { +		$set = "(" . implode(',', $guids) . ")"; +		$sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set"; +		$secondary_rows = get_data($sql); +		if ($secondary_rows) { +			foreach ($secondary_rows as $secondary_row) { +				$key = $guid_to_key[$secondary_row->guid]; +				// cast to arrays to merge then cast back +				$rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row); +			} +		} +	} +	// Second pass to finish conversion +	foreach ($rows as $i => $row) { +		if ($row instanceof ElggEntity) { +			continue; +		} else { +			try { +				$rows[$i] = entity_row_to_elggstar($row); +			} catch (IncompleteEntityException $e) { +				// don't let incomplete entities throw fatal errors +				unset($rows[$i]); +			} +		} +	} +	return $rows; +} + +/**   * Returns SQL where clause for type and subtype on main entity table   *   * @param string     $table    Entity table prefix as defined in SELECT...FROM entities $table @@ -1153,7 +1314,7 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair   *                           best to provide in table.column format.   * @param NULL|array $guids  Array of GUIDs.   * - * @return false|str + * @return false|string   * @since 1.8.0   * @access private   */ @@ -1202,7 +1363,7 @@ function elgg_get_guid_based_where_sql($column, $guids) {   * @param NULL|int $time_updated_upper Time updated upper limit   * @param NULL|int $time_updated_lower Time updated lower limit   * - * @return FALSE|str FALSE on fail, string on success. + * @return FALSE|string FALSE on fail, string on success.   * @since 1.7.0   * @access private   */ @@ -1304,7 +1465,7 @@ function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entiti   * @param string $subtype        The subtype of entity   * @param int    $container_guid The container GUID that the entinties belong to   * @param int    $site_guid      The site GUID - * @param str    $order_by       Order_by SQL order by clause + * @param string $order_by       Order_by SQL order by clause   *   * @return array|false Either an array months as YYYYMM, or false on failure   */ @@ -1649,7 +1810,7 @@ function delete_entity($guid, $recursive = true) {   * @param string $returnvalue Return value from previous hook   * @param array  $params      The parameters, passed 'guid' and 'varname'   * - * @return void + * @return ElggMetadata|null   * @elgg_plugin_hook_handler volatile metadata   * @todo investigate more.   * @access private @@ -1694,6 +1855,8 @@ function volatile_data_export_plugin_hook($hook, $entity_type, $returnvalue, $pa   * @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 @@ -1736,6 +1899,8 @@ function export_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) {   * @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'); @@ -1747,7 +1912,7 @@ function oddentity_to_elggentity(ODDEntity $element) {  	if (!$tmp) {  		// Construct new class with owner from session  		$classname = get_subtype_class($class, $subclass); -		if ($classname != "") { +		if ($classname) {  			if (class_exists($classname)) {  				$tmp = new $classname(); @@ -1807,11 +1972,13 @@ function oddentity_to_elggentity(ODDEntity $element) {   * @elgg_plugin_hook_handler import all   * @todo document   * @access private + * + * @throws ImportException   */  function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) {  	$element = $params['element']; -	$tmp = NULL; +	$tmp = null;  	if ($element instanceof ODDEntity) {  		$tmp = oddentity_to_elggentity($element); @@ -1853,8 +2020,6 @@ function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) {   * @link http://docs.elgg.org/Entities/AccessControl   */  function can_edit_entity($entity_guid, $user_guid = 0) { -	global $CONFIG; -  	$user_guid = (int)$user_guid;  	$user = get_entity($user_guid);  	if (!$user) { @@ -1978,7 +2143,7 @@ function get_entity_url($entity_guid) {   * @param string $entity_subtype The entity subtype   * @param string $function_name  The function to register   * - * @return true|false Depending on success + * @return bool Depending on success   * @see get_entity_url()   * @see ElggEntity::getURL()   * @since 1.8.0 @@ -1986,7 +2151,7 @@ function get_entity_url($entity_guid) {  function elgg_register_entity_url_handler($entity_type, $entity_subtype, $function_name) {  	global $CONFIG; -	if (!is_callable($function_name)) { +	if (!is_callable($function_name, true)) {  		return false;  	} @@ -2014,7 +2179,7 @@ function elgg_register_entity_url_handler($entity_type, $entity_subtype, $functi   * @param string $type    The type of entity (object, site, user, group)   * @param string $subtype The subtype to register (may be blank)   * - * @return true|false Depending on success + * @return bool Depending on success   * @see get_registered_entity_types()   * @link http://docs.elgg.org/Search   * @link http://docs.elgg.org/Tutorials/Search @@ -2051,7 +2216,7 @@ function elgg_register_entity_type($type, $subtype = null) {   * @param string $type    The type of entity (object, site, user, group)   * @param string $subtype The subtype to register (may be blank)   * - * @return true|false Depending on success + * @return bool Depending on success   * @see elgg_register_entity_type()   */  function unregister_entity_type($type, $subtype) { @@ -2118,7 +2283,7 @@ function get_registered_entity_types($type = null) {   * @param string $type    The type of entity (object, site, user, group)   * @param string $subtype The subtype (may be blank)   * - * @return true|false Depending on whether or not the type has been registered + * @return bool Depending on whether or not the type has been registered   */  function is_registered_entity_type($type, $subtype = null) {  	global $CONFIG; @@ -2318,7 +2483,7 @@ function entities_gc() {  /**   * Runs unit tests for the entity objects.   * - * @param sting  $hook   unit_test + * @param string  $hook   unit_test   * @param string $type   system   * @param mixed  $value  Array of tests   * @param mixed  $params Params diff --git a/engine/lib/extender.php b/engine/lib/extender.php index 43421342c..538f601e1 100644 --- a/engine/lib/extender.php +++ b/engine/lib/extender.php @@ -136,14 +136,15 @@ function can_edit_extender($extender_id, $type, $user_guid = 0) {  	$functionname = "elgg_get_{$type}_from_id";  	if (is_callable($functionname)) { -		$extender = $functionname($extender_id); +		$extender = call_user_func($functionname, $extender_id);  	} else {  		return false;  	} -	if (!is_a($extender, "ElggExtender")) { +	if (!($extender instanceof ElggExtender)) {  		return false;  	} +	/* @var ElggExtender $extender */  	// If the owner is the specified user, great! They can edit.  	if ($extender->getOwnerGUID() == $user->getGUID()) { @@ -175,7 +176,7 @@ function elgg_register_extender_url_handler($extender_type, $extender_name, $fun  	global $CONFIG; -	if (!is_callable($function_name)) { +	if (!is_callable($function_name, true)) {  		return false;  	} @@ -228,7 +229,7 @@ function get_extender_url(ElggExtender $extender) {  	if ($url == "") {  		$nameid = $extender->id;  		if ($type == 'volatile') { -			$nameid == $extender->name; +			$nameid = $extender->name;  		}  		$url = "export/$view/$guid/$type/$nameid/";  	} diff --git a/engine/lib/group.php b/engine/lib/group.php index b32c4bd48..5a38e1ea6 100644 --- a/engine/lib/group.php +++ b/engine/lib/group.php @@ -33,6 +33,7 @@ function get_group_entity_as_row($guid) {   * @param string $description Description   *   * @return bool + * @access private   */  function create_group_entity($guid, $name, $description) {  	global $CONFIG; diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php index 77fa30e41..f76c20f24 100644 --- a/engine/lib/metadata.php +++ b/engine/lib/metadata.php @@ -12,7 +12,7 @@   *   * @param stdClass $row An object from the database   * - * @return stdClass or ElggMetadata + * @return stdClass|ElggMetadata   * @access private   */  function row_to_elggmetadata($row) { @@ -30,7 +30,7 @@ function row_to_elggmetadata($row) {   *   * @param int $id The id of the metadata object being retrieved.   * - * @return false|ElggMetadata + * @return ElggMetadata|false  FALSE if not found   */  function elgg_get_metadata_from_id($id) {  	return elgg_get_metastring_based_object_from_id($id, 'metadata'); @@ -64,7 +64,7 @@ function elgg_delete_metadata_by_id($id) {   * @param int    $access_id      Default is ACCESS_PRIVATE   * @param bool   $allow_multiple Allow multiple values for one key. Default is FALSE   * - * @return int/bool id of metadata or FALSE if failure + * @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) { @@ -90,8 +90,6 @@ function create_metadata($entity_guid, $name, $value, $value_type = '', $owner_g  	$access_id = (int)$access_id; -	$id = false; -  	$query = "SELECT * from {$CONFIG->dbprefix}metadata"  		. " WHERE entity_guid = $entity_guid and name_id=" . add_metastring($name) . " limit 1"; @@ -106,34 +104,33 @@ function create_metadata($entity_guid, $name, $value, $value_type = '', $owner_g  	} else {  		// Support boolean types  		if (is_bool($value)) { -			if ($value) { -				$value = 1; -			} else { -				$value = 0; -			} +			$value = (int) $value;  		}  		// Add the metastrings -		$value = add_metastring($value); -		if (!$value) { +		$value_id = add_metastring($value); +		if (!$value_id) {  			return false;  		} -		$name = add_metastring($name); -		if (!$name) { +		$name_id = add_metastring($name); +		if (!$name_id) {  			return false;  		}  		// If ok then add it  		$query = "INSERT into {$CONFIG->dbprefix}metadata"  			. " (entity_guid, name_id, value_id, value_type, owner_guid, time_created, access_id)" -			. " VALUES ($entity_guid, '$name','$value','$value_type', $owner_guid, $time, $access_id)"; +			. " 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); @@ -175,6 +172,7 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i  	}  	if ($metabyname_memcache) { +		// @todo fix memcache (name_id is not a property of ElggMetadata)  		$metabyname_memcache->delete("{$md->entity_guid}:{$md->name_id}");  	} @@ -187,15 +185,9 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i  	$access_id = (int)$access_id; -	$access = get_access_sql_suffix(); -  	// Support boolean types (as integers)  	if (is_bool($value)) { -		if ($value) { -			$value = 1; -		} else { -			$value = 0; -		} +		$value = (int) $value;  	}  	// Add the metastring @@ -216,6 +208,9 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i  	$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. @@ -234,7 +229,7 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i   * 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 string $name_and_values Associative array - a value can be a string, number, bool + * @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 @@ -308,6 +303,8 @@ function elgg_delete_metadata(array $options) {  		return false;  	} +	elgg_get_metadata_cache()->invalidateByOptions('delete', $options); +  	$options['metastring_type'] = 'metadata';  	return elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false);  } @@ -328,6 +325,8 @@ function elgg_disable_metadata(array $options) {  		return false;  	} +	elgg_get_metadata_cache()->invalidateByOptions('disable', $options); +  	$options['metastring_type'] = 'metadata';  	return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', false);  } @@ -348,6 +347,8 @@ function elgg_enable_metadata(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');  } @@ -449,16 +450,16 @@ function elgg_get_entities_from_metadata(array $options = array()) {   * 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, + * @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 arr|null $names             Array of names - * @param arr|null $values            Array of values - * @param arr|null $pairs             Array of names / values / operands - * @param and|or   $pair_operator     Operator to use to join the where clauses for pairs - * @param bool     $case_sensitive    Case sensitive metadata names? - * @param arr|null $order_by_metadata Array of names / direction - * @param arr|null $owner_guids       Array of owner GUIDs + * @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 @@ -732,6 +733,8 @@ function elgg_list_entities_from_metadata($options) {   *   * @return array   * @access private + * + * @throws InvalidParameterException   */  function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params) {  	// Sanity check values @@ -743,15 +746,13 @@ function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params)  		throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonArrayReturnValue'));  	} -	$guid = (int)$params['guid']; -	$name = $params['name']; -  	$result = elgg_get_metadata(array( -		'guid' => $guid, -		'limit' => 0 +		'guid' => (int)$params['guid'], +		'limit' => 0,  	));  	if ($result) { +		/* @var ElggMetadata[] $result */  		foreach ($result as $r) {  			$returnvalue[] = $r->export();  		} @@ -889,6 +890,50 @@ function elgg_register_metadata_url_handler($extender_name, $function) {  	return elgg_register_extender_url_handler('metadata', $extender_name, $function);  } +/** + * Get the global metadata cache instance + * + * @return ElggVolatileMetadataCache + * + * @access private + */ +function elgg_get_metadata_cache() { +	global $CONFIG; +	if (empty($CONFIG->local_metadata_cache)) { +		$CONFIG->local_metadata_cache = new ElggVolatileMetadataCache(); +	} +	return $CONFIG->local_metadata_cache; +} + +/** + * Invalidate the metadata cache based on options passed to various *_metadata functions + * + * @param string $action  Action performed on metadata. "delete", "disable", or "enable" + * + * @param array $options  Options passed to elgg_(delete|disable|enable)_metadata + */ +function elgg_invalidate_metadata_cache($action, array $options) { +	// remove as little as possible, optimizing for common cases +	$cache = elgg_get_metadata_cache(); +	if (empty($options['guid'])) { +		// safest to clear everything unless we want to make this even more complex :( +		$cache->flush(); +	} else { +		if (empty($options['metadata_name'])) { +			// safest to clear the whole entity +			$cache->clear($options['guid']); +		} else { +			switch ($action) { +				case 'delete': +					$cache->markEmpty($options['guid'], $options['metadata_name']); +					break; +				default: +					$cache->markUnknown($options['guid'], $options['metadata_name']); +			} +		} +	} +} +  /** Register the hook */  elgg_register_plugin_hook_handler("export", "all", "export_metadata_plugin_hook", 2); @@ -912,5 +957,6 @@ elgg_register_plugin_hook_handler('unit_test', 'system', 'metadata_test');  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; -}
\ No newline at end of file +} diff --git a/engine/lib/navigation.php b/engine/lib/navigation.php index 8c3952594..86624cd7c 100644 --- a/engine/lib/navigation.php +++ b/engine/lib/navigation.php @@ -308,6 +308,32 @@ function elgg_site_menu_setup($hook, $type, $return, $params) {  			$return['more'] = array_splice($return['default'], $max_display_items);  		}  	} +	 +	// check if we have anything selected +	$selected = false; +	foreach ($return as $section_name => $section) { +		foreach ($section as $key => $item) { +			if ($item->getSelected()) { +				$selected = true; +				break 2; +			} +		} +	} +	 +	if (!$selected) { +		// nothing selected, match name to context +		foreach ($return as $section_name => $section) { +			foreach ($section as $key => $item) { +				// only highlight internal links +				if (strpos($item->getHref(), elgg_get_site_url()) === 0) { +					if ($item->getName() == elgg_get_context()) { +						$return[$section_name][$key]->setSelected(true); +						break 2; +					} +				} +			} +		} +	}  	return $return;  } diff --git a/engine/lib/notification.php b/engine/lib/notification.php index 18faff27f..9e3c075a8 100644 --- a/engine/lib/notification.php +++ b/engine/lib/notification.php @@ -38,7 +38,7 @@ $NOTIFICATION_HANDLERS = array();  function register_notification_handler($method, $handler, $params = NULL) {  	global $NOTIFICATION_HANDLERS; -	if (is_callable($handler)) { +	if (is_callable($handler, true)) {  		$NOTIFICATION_HANDLERS[$method] = new stdClass;  		$NOTIFICATION_HANDLERS[$method]->handler = $handler; @@ -131,8 +131,9 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth  					// Extract method details from list  					$details = $NOTIFICATION_HANDLERS[$method];  					$handler = $details->handler; +					/* @var callable $handler */ -					if ((!$NOTIFICATION_HANDLERS[$method]) || (!$handler)) { +					if ((!$NOTIFICATION_HANDLERS[$method]) || (!$handler) || (!is_callable($handler))) {  						error_log(elgg_echo('NotificationException:NoHandlerFound', array($method)));  					} @@ -140,7 +141,7 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth  					// Trigger handler and retrieve result.  					try { -						$result[$guid][$method] = $handler( +						$result[$guid][$method] = call_user_func($handler,  							$from ? get_entity($from) : NULL, 	// From entity  							get_entity($guid), 					// To entity  							$subject,							// The subject diff --git a/engine/lib/objects.php b/engine/lib/objects.php index f186c66cb..e5e8f67c4 100644 --- a/engine/lib/objects.php +++ b/engine/lib/objects.php @@ -31,6 +31,7 @@ function get_object_entity_as_row($guid) {   * @param string $description The object's description   *   * @return bool + * @access private   */  function create_object_entity($guid, $title, $description) {  	global $CONFIG; diff --git a/engine/lib/output.php b/engine/lib/output.php index 7bfc4be6e..bff0bf6e9 100644 --- a/engine/lib/output.php +++ b/engine/lib/output.php @@ -16,7 +16,7 @@   **/  function parse_urls($text) {  	// @todo this causes problems with <attr = "val"> -	// must be ing <attr="val"> format (no space). +	// 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', @@ -43,51 +43,26 @@ function parse_urls($text) {  /**   * Create paragraphs from text with line spacing - * Borrowed from Wordpress.   *   * @param string $pee The string - * @param bool   $br  Add BRs? + * @deprecated Use elgg_autop instead + * @todo Add deprecation warning in 1.9   * - * @todo Rewrite   * @return string   **/ -function autop($pee, $br = 1) { -	$pee = $pee . "\n"; // just to make things a little easier, pad the end -	$pee = preg_replace('|<br />\s*<br />|', "\n\n", $pee); -	// Space things out a little -	$allblocks = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|map|area|blockquote|address|math|style|input|p|h[1-6]|hr)'; -	$pee = preg_replace('!(<' . $allblocks . '[^>]*>)!', "\n$1", $pee); -	$pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee); -	$pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines -	if (strpos($pee, '<object') !== false) { -		$pee = preg_replace('|\s*<param([^>]*)>\s*|', "<param$1>", $pee); // no pee inside object/embed -		$pee = preg_replace('|\s*</embed>\s*|', '</embed>', $pee); -	} -	$pee = preg_replace("/\n\n+/", "\n\n", $pee); // take care of duplicates -	$pee = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "<p>$1</p>\n", $pee); // make paragraphs, including one at the end -	$pee = preg_replace('|<p>\s*?</p>|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace -	$pee = preg_replace('!<p>([^<]+)\s*?(</(?:div|address|form)[^>]*>)!', "<p>$1</p>$2", $pee); -	$pee = preg_replace('|<p>|', "$1<p>", $pee); -	$pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); // don't pee all over a tag -	$pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // problem with nested lists -	$pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee); -	$pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee); -	$pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee); -	$pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); -	if ($br) { -		$pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', create_function('$matches', 'return str_replace("\n", "<WPPreserveNewline />", $matches[0]);'), $pee); -		$pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee); // optionally make line breaks -		$pee = str_replace('<WPPreserveNewline />', "\n", $pee); -	} -	$pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', "$1", $pee); -	$pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee); -	//if (strpos($pee, '<pre') !== false) { -	//	mind the space between the ? and >.  Only there because of the comment. -	//	$pee = preg_replace_callback('!(<pre.*? >)(.*?)</pre>!is', 'clean_pre', $pee ); -	//} -	$pee = preg_replace("|\n</p>$|", '</p>', $pee); - -	return $pee; +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);  }  /** @@ -271,8 +246,8 @@ function elgg_normalize_url($url) {  		// '?query=test', #target  		return $url; -	} elseif (stripos($url, 'javascript:') === 0) { -		// 'javascript:' +	} elseif (stripos($url, 'javascript:') === 0 || stripos($url, 'mailto:') === 0) { +		// 'javascript:' and 'mailto:'  		// Not covered in FILTER_VALIDATE_URL  		return $url; @@ -384,7 +359,7 @@ function elgg_get_friendly_time($time) {  /**   * Strip tags and offer plugins the chance.   * Plugins register for output:strip_tags plugin hook. - * 	Original string included in $params['original_string'] + * Original string included in $params['original_string']   *   * @param string $string Formatted string   * @@ -398,3 +373,74 @@ function elgg_strip_tags($string) {  	return $string;  } + +/** + * Apply html_entity_decode() to a string while re-entitising HTML + * special char entities to prevent them from being decoded back to their + * unsafe original forms. + * + * This relies on html_entity_decode() not translating entities when + * doing so leaves behind another entity, e.g. &gt; if decoded would + * create > which is another entity itself. This seems to escape the + * usual behaviour where any two paired entities creating a HTML tag are + * usually decoded, i.e. a lone > is not decoded, but <foo> would + * be decoded to <foo> since it creates a full tag. + * + * Note: This function is poorly explained in the manual - which is really + * bad given its potential for misuse on user input already escaped elsewhere. + * Stackoverflow is littered with advice to use this function in the precise + * way that would lead to user input being capable of injecting arbitrary HTML. + * + * @param string $string + * + * @return string + * + * @author Pádraic Brady + * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com) + * @license Released under dual-license GPL2/MIT by explicit permission of Pádraic Brady + * + * @access private + */ +function _elgg_html_decode($string) { +	$string = str_replace( +		array('>', '<', '&', '"', '''), +		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), +		$string +	); +	$string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8'); +	$string = str_replace( +		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), +		array('>', '<', '&', '"', '''), +		$string +	); +	return $string; +} + +/** + * Unit tests for Output + * + * @param sting  $hook   unit_test + * @param string $type   system + * @param mixed  $value  Array of tests + * @param mixed  $params Params + * + * @return array + * @access private + */ +function output_unit_test($hook, $type, $value, $params) { +	global $CONFIG; +	$value[] = $CONFIG->path . 'engine/tests/api/output.php'; +	return $value; +} + +/** + * Initialise the Output subsystem. + * + * @return void + * @access private + */ +function output_init() { +	elgg_register_plugin_hook_handler('unit_test', 'system', 'output_unit_test'); +} + +elgg_register_event_handler('init', 'system', 'output_init'); diff --git a/engine/lib/pagehandler.php b/engine/lib/pagehandler.php index ba7518a77..0cf99b6fe 100644 --- a/engine/lib/pagehandler.php +++ b/engine/lib/pagehandler.php @@ -45,7 +45,10 @@ function page_handler($handler, $page) {  	$page = $request['segments'];  	$result = false; -	if (isset($CONFIG->pagehandler) && !empty($handler) && isset($CONFIG->pagehandler[$handler])) { +	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);  	} @@ -76,14 +79,15 @@ function page_handler($handler, $page) {   * @param string $handler  The page type to handle   * @param string $function Your function name   * - * @return true|false Depending on success + * @return bool Depending on success   */  function elgg_register_page_handler($handler, $function) {  	global $CONFIG; +  	if (!isset($CONFIG->pagehandler)) {  		$CONFIG->pagehandler = array();  	} -	if (is_callable($function)) { +	if (is_callable($function, true)) {  		$CONFIG->pagehandler[$handler] = $function;  		return true;  	} diff --git a/engine/lib/pageowner.php b/engine/lib/pageowner.php index 0cf0e0625..94765feee 100644 --- a/engine/lib/pageowner.php +++ b/engine/lib/pageowner.php @@ -37,6 +37,8 @@ function elgg_get_page_owner_guid($guid = 0) {  /**   * Gets the owner entity for the current page.   * + * @note Access is disabled when getting the page owner entity. + *   * @return ElggEntity|false The current page owner or false if none.   *   * @since 1.8.0 @@ -44,10 +46,14 @@ function elgg_get_page_owner_guid($guid = 0) {  function elgg_get_page_owner_entity() {  	$guid = elgg_get_page_owner_guid();  	if ($guid > 0) { -		return get_entity($guid); +		$ia = elgg_set_ignore_access(true); +		$owner = get_entity($guid); +		elgg_set_ignore_access($ia); + +		return $owner;  	} -	return FALSE; +	return false;  }  /** @@ -75,6 +81,8 @@ function elgg_set_page_owner_guid($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' @@ -90,6 +98,8 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)  		return $returnvalue;  	} +	$ia = elgg_set_ignore_access(true); +  	$username = get_input("username");  	if ($username) {  		// @todo using a username of group:<guid> is deprecated @@ -97,6 +107,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)  			preg_match('/group\:([0-9]+)/i', $username, $matches);  			$guid = $matches[1];  			if ($entity = get_entity($guid)) { +				elgg_set_ignore_access($ia);  				return $entity->getGUID();  			}  		} @@ -109,6 +120,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)  	$owner = get_input("owner_guid");  	if ($owner) {  		if ($user = get_entity($owner)) { +			elgg_set_ignore_access($ia);  			return $user->getGUID();  		}  	} @@ -130,6 +142,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)  				case 'friends':  					$user = get_user_by_username($segments[2]);  					if ($user) { +						elgg_set_ignore_access($ia);  						return $user->getGUID();  					}  					break; @@ -137,6 +150,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)  				case 'edit':  					$entity = get_entity($segments[2]);  					if ($entity) { +						elgg_set_ignore_access($ia);  						return $entity->getContainerGUID();  					}  					break; @@ -144,6 +158,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)  				case 'group':  					$entity = get_entity($segments[2]);  					if ($entity) { +						elgg_set_ignore_access($ia);  						return $entity->getGUID();  					}  					break; @@ -151,7 +166,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)  		}  	} -	return $returnvalue; +	elgg_set_ignore_access($ia);  }  /** diff --git a/engine/lib/pam.php b/engine/lib/pam.php index 4f9f44278..1c9c3bfe1 100644 --- a/engine/lib/pam.php +++ b/engine/lib/pam.php @@ -30,7 +30,9 @@ $_PAM_HANDLERS = array();   * failure, return false or throw an exception. Returning nothing indicates that   * the handler wants to be skipped.   * - * @param string $handler    The handler function in the format + * 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" @@ -45,7 +47,8 @@ function register_pam_handler($handler, $importance = "sufficient", $policy = "u  		$_PAM_HANDLERS[$policy] = array();  	} -	if (is_callable($handler)) { +	// @todo remove requirement that $handle be a global function +	if (is_string($handler) && is_callable($handler, true)) {  		$_PAM_HANDLERS[$policy][$handler] = new stdClass;  		$_PAM_HANDLERS[$policy][$handler]->handler = $handler; diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php index d5cd4fe76..94aff277e 100644 --- a/engine/lib/plugins.php +++ b/engine/lib/plugins.php @@ -176,6 +176,19 @@ function elgg_generate_plugin_entities() {  }  /** + * 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. @@ -183,6 +196,11 @@ function elgg_generate_plugin_entities() {   * @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'); @@ -190,6 +208,7 @@ function elgg_get_plugin_from_id($plugin_id) {  		'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  	); @@ -512,6 +531,8 @@ function elgg_namespace_plugin_private_setting($type, $name, $id = null) {   * @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) { @@ -920,6 +941,7 @@ function elgg_set_plugin_setting($name, $value, $plugin_id = null) {   *   * @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) { diff --git a/engine/lib/relationships.php b/engine/lib/relationships.php index 09d541e22..01654b1ce 100644 --- a/engine/lib/relationships.php +++ b/engine/lib/relationships.php @@ -416,7 +416,7 @@ function elgg_list_entities_from_relationship_count($options) {  function elgg_register_relationship_url_handler($relationship_type, $function_name) {  	global $CONFIG; -	if (!is_callable($function_name)) { +	if (!is_callable($function_name, true)) {  		return false;  	} diff --git a/engine/lib/river.php b/engine/lib/river.php index b717a7756..33f34360e 100644 --- a/engine/lib/river.php +++ b/engine/lib/river.php @@ -361,6 +361,7 @@ function elgg_get_river(array $options = array()) {  		}  		$river_items = get_data($query, 'elgg_row_to_elgg_river_item'); +		_elgg_prefetch_river_entities($river_items);  		return $river_items;  	} else { @@ -370,11 +371,56 @@ function elgg_get_river(array $options = array()) {  }  /** + * Prefetch entities that will be displayed in the river. + * + * @param ElggRiverItem[] $river_items + * @access private + */ +function _elgg_prefetch_river_entities(array $river_items) { +	// prefetch objects and subjects +	$guids = array(); +	foreach ($river_items as $item) { +		if ($item->subject_guid && !retrieve_cached_entity($item->subject_guid)) { +			$guids[$item->subject_guid] = true; +		} +		if ($item->object_guid && !retrieve_cached_entity($item->object_guid)) { +			$guids[$item->object_guid] = true; +		} +	} +	if ($guids) { +		// avoid creating oversized query +		// @todo how to better handle this? +		$guids = array_slice($guids, 0, 300, true); +		// return value unneeded, just priming cache +		elgg_get_entities(array( +			'guids' => array_keys($guids), +			'limit' => 0, +		)); +	} + +	// prefetch object containers +	$guids = array(); +	foreach ($river_items as $item) { +		$object = $item->getObjectEntity(); +		if ($object->container_guid && !retrieve_cached_entity($object->container_guid)) { +			$guids[$object->container_guid] = true; +		} +	} +	if ($guids) { +		$guids = array_slice($guids, 0, 300, true); +		elgg_get_entities(array( +			'guids' => array_keys($guids), +			'limit' => 0, +		)); +	} +} + +/**   * List river items   *   * @param array $options Any options from elgg_get_river() plus:   * 	 pagination => BOOL Display pagination links (true) - + *   * @return string   * @since 1.8.0   */ diff --git a/engine/lib/sites.php b/engine/lib/sites.php index 850092cad..d9eb2d25e 100644 --- a/engine/lib/sites.php +++ b/engine/lib/sites.php @@ -18,11 +18,19 @@  function elgg_get_site_entity($site_guid = 0) {  	global $CONFIG; +	$result = false; +	  	if ($site_guid == 0) { -		return $CONFIG->site; +		$site = $CONFIG->site; +	} else { +		$site = get_entity($site_guid); +	} +	 +	if($site instanceof ElggSite){ +		$result = $site;  	} -	return get_entity($site_guid); +	return $result;  }  /** @@ -50,6 +58,7 @@ function get_site_entity_as_row($guid) {   * @param string $url         URL of the site   *   * @return bool + * @access private   */  function create_site_entity($guid, $name, $description, $url) {  	global $CONFIG; diff --git a/engine/lib/upgrade.php b/engine/lib/upgrade.php index f0874a483..f4f4b16f5 100644 --- a/engine/lib/upgrade.php +++ b/engine/lib/upgrade.php @@ -311,3 +311,58 @@ function elgg_upgrade_bootstrap_17_to_18() {  	return elgg_set_processed_upgrades($processed_upgrades);  } + +/** + * Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades. + * + * @see _elgg_upgrade_lock() + * + * @return bool + * @access private + */ +function _elgg_upgrade_lock() { +	global $CONFIG; +	 +	if (!_elgg_upgrade_is_locked()) { +		// lock it +		insert_data("create table {$CONFIG->dbprefix}upgrade_lock (id INT)"); +		elgg_log('Locked for upgrade.', 'NOTICE'); +		return true; +	} +	 +	elgg_log('Cannot lock for upgrade: already locked.', 'WARNING'); +	return false; +} + +/** + * Unlocks upgrade. + * + * @see _elgg_upgrade_lock() + * + * @access private + */ +function _elgg_upgrade_unlock() { +	global $CONFIG; +	delete_data("drop table {$CONFIG->dbprefix}upgrade_lock"); +	elgg_log('Upgrade unlocked.', 'NOTICE'); +} + +/** + * Checks if upgrade is locked + * + * @return bool + * @access private + */ +function _elgg_upgrade_is_locked() { +	global $CONFIG, $DB_QUERY_CACHE; +	 +	$is_locked = count(get_data("show tables like '{$CONFIG->dbprefix}upgrade_lock'")); +	 +	// Invalidate query cache +	if ($DB_QUERY_CACHE) { +		$DB_QUERY_CACHE->clear(); +		elgg_log("Query cache invalidated", 'NOTICE'); +	} +	 +	return $is_locked; +} diff --git a/engine/lib/upgrades/2010052601.php b/engine/lib/upgrades/2010052601.php index 5b477910f..a9cca6dc5 100644 --- a/engine/lib/upgrades/2010052601.php +++ b/engine/lib/upgrades/2010052601.php @@ -9,14 +9,14 @@ $params = array('type' => 'group',  $groups = elgg_get_entities($params);  if ($groups) {  	foreach ($groups as $group) { -		$group->name = html_entity_decode($group->name, ENT_COMPAT, 'UTF-8'); -		$group->description = html_entity_decode($group->description, ENT_COMPAT, 'UTF-8'); -		$group->briefdescription = html_entity_decode($group->briefdescription, ENT_COMPAT, 'UTF-8'); -		$group->website = html_entity_decode($group->website, ENT_COMPAT, 'UTF-8'); +		$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] = html_entity_decode($tag, ENT_COMPAT, 'UTF-8'); +			foreach ($tags as $index => $tag) { +				$tags[$index] = _elgg_html_decode($tag);  			}  			$group->interests = $tags;  		} diff --git a/engine/lib/users.php b/engine/lib/users.php index 527eff3cd..95ef9d176 100644 --- a/engine/lib/users.php +++ b/engine/lib/users.php @@ -44,6 +44,7 @@ function get_user_entity_as_row($guid) {   * @param string $code     A code   *   * @return bool + * @access private   */  function create_user_entity($guid, $name, $username, $password, $salt, $email, $language, $code) {  	global $CONFIG; diff --git a/engine/lib/views.php b/engine/lib/views.php index 90737c260..c1b616cf1 100644 --- a/engine/lib/views.php +++ b/engine/lib/views.php @@ -101,15 +101,15 @@ function elgg_get_viewtype() {  		return $CURRENT_SYSTEM_VIEWTYPE;  	} -	$viewtype = get_input('view', NULL); -	if ($viewtype) { +	$viewtype = get_input('view', '', false); +	if (is_string($viewtype) && $viewtype !== '') {  		// only word characters allowed. -		if (!preg_match('[\W]', $viewtype)) { +		if (!preg_match('/\W/', $viewtype)) {  			return $viewtype;  		}  	} -	if (isset($CONFIG->view) && !empty($CONFIG->view)) { +	if (!empty($CONFIG->view)) {  		return $CONFIG->view;  	} @@ -258,8 +258,6 @@ function elgg_get_view_location($view, $viewtype = '') {  	} else {  		return $CONFIG->views->locations[$viewtype][$view];  	} - -	return false;  }  /** @@ -329,7 +327,7 @@ function elgg_view_exists($view, $viewtype = '', $recurse = true) {  		$location = $CONFIG->views->locations[$viewtype][$view];  	} -	if (file_exists($location . "{$viewtype}/{$view}.php")) { +	if (file_exists("{$location}{$viewtype}/{$view}.php")) {  		return true;  	} @@ -378,7 +376,7 @@ function elgg_view_exists($view, $viewtype = '', $recurse = true) {   * @param boolean $bypass   If set to true, elgg_view will bypass any specified   *                          alternative template handler; by default, it will   *                          hand off to this if requested (see set_template_handler) - * @param boolean $debug    If set to true, the viewer will complain if it can't find a view + * @param 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)   * @@ -386,18 +384,30 @@ function elgg_view_exists($view, $viewtype = '', $recurse = true) {   * @see set_template_handler()   * @example views/elgg_view.php   * @link http://docs.elgg.org/View - * @todo $debug isn't used. - * @todo $usercache is redundant.   */ -function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $viewtype = '') { +function elgg_view($view, $vars = array(), $bypass = false, $ignored = false, $viewtype = '') {  	global $CONFIG; -	static $usercache; - -	$view = (string)$view; +	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 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; @@ -408,19 +418,6 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie  		elgg_trigger_event('pagesetup', 'system');  	} -	if (!is_array($usercache)) { -		$usercache = array(); -	} - -	if (!is_array($vars)) { -		elgg_log("Vars in views must be an array: $view", 'ERROR'); -		$vars = array(); -	} - -	if (empty($vars)) { -		$vars = array(); -	} -  	// @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. @@ -475,16 +472,6 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie  		}  	} -	// Get the current viewtype -	if (empty($viewtype)) { -		$viewtype = elgg_get_viewtype(); -	} - -	// Viewtypes can only be alphanumeric -	if (preg_match('[\W]', $viewtype)) { -		return ''; -	} -  	// Set up any extensions to the requested view  	if (isset($CONFIG->views->extensions[$view])) {  		$viewlist = $CONFIG->views->extensions[$view]; @@ -496,19 +483,21 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie  	ob_start();  	foreach ($viewlist as $priority => $view) { +  		$view_location = elgg_get_view_location($view, $viewtype);  		$view_file = "$view_location$viewtype/$view.php"; -		$default_location = elgg_get_view_location($view, 'default'); -		$default_view_file = "{$default_location}default/$view.php"; -  		// try to include view  		if (!file_exists($view_file) || !include($view_file)) {  			// requested view does not exist  			$error = "$viewtype/$view view does not exist.";  			// attempt to load default view -			if ($viewtype != 'default' && elgg_does_viewtype_fallback($viewtype)) { +			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."; @@ -533,7 +522,7 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie  	// 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) { +	if ($content_tmp !== $content) {  		$content = $content_tmp;  		elgg_deprecated_notice('The display:view plugin hook is deprecated by view:view_name', 1.8);  	} @@ -559,33 +548,32 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie   * @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) - * @param string $viewtype       Not used   *   * @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, $viewtype = '') { +function elgg_extend_view($view, $view_extension, $priority = 501) {  	global $CONFIG;  	if (!isset($CONFIG->views)) { -		$CONFIG->views = new stdClass; -	} - -	if (!isset($CONFIG->views->extensions)) { -		$CONFIG->views->extensions = array(); -	} - -	if (!isset($CONFIG->views->extensions[$view])) { -		$CONFIG->views->extensions[$view][500] = "{$view}"; +		$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] = "{$view_extension}"; +	$CONFIG->views->extensions[$view][$priority] = (string)$view_extension;  	ksort($CONFIG->views->extensions[$view]);  } @@ -601,14 +589,6 @@ function elgg_extend_view($view, $view_extension, $priority = 501, $viewtype = '  function elgg_unextend_view($view, $view_extension) {  	global $CONFIG; -	if (!isset($CONFIG->views)) { -		return FALSE; -	} - -	if (!isset($CONFIG->views->extensions)) { -		return FALSE; -	} -  	if (!isset($CONFIG->views->extensions[$view])) {  		return FALSE;  	} @@ -1105,10 +1085,6 @@ function elgg_view_annotation_list($annotations, array $vars = array()) {   * @todo Change the hook name.   */  function elgg_view_entity_annotations(ElggEntity $entity, $full_view = true) { -	if (!$entity) { -		return false; -	} -  	if (!($entity instanceof ElggEntity)) {  		return false;  	} @@ -1131,7 +1107,7 @@ function elgg_view_entity_annotations(ElggEntity $entity, $full_view = true) {   * This is a shortcut for {@elgg_view page/elements/title}.   *   * @param string $title The page title - * @param string $vars  View variables (was submenu be displayed? (deprecated)) + * @param array $vars   View variables (was submenu be displayed? (deprecated))   *   * @return string The HTML (etc)   */ @@ -1203,7 +1179,7 @@ function elgg_view_comments($entity, $add_comment = true, array $vars = array())   *   * @param string $image The icon and other information   * @param string $body  Description content - * @param string $vars  Additional parameters for the view + * @param array $vars   Additional parameters for the view   *   * @return string   * @since 1.8.0 @@ -1230,7 +1206,6 @@ function elgg_view_image_block($image, $body, $vars = array()) {   * @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; @@ -1243,11 +1218,15 @@ function elgg_view_module($type, $title, $body, array $vars = array()) {   * @param ElggRiverItem $item A river item object   * @param array         $vars An array of variables for the view   * - * @return string + * @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) -	if (!$item || !$item->getView() || !elgg_view_exists($item->getView(), 'default')) { +	$view = $item->getView(); +	if (!$view || !elgg_view_exists($view, 'default')) {  		return '';  	} @@ -1345,7 +1324,7 @@ function elgg_view_list_item($item, array $vars = array()) {  		return elgg_view_river_item($item, $vars);  	} -	return false; +	return '';  }  /** @@ -1360,7 +1339,7 @@ function elgg_view_list_item($item, array $vars = array()) {   */  function elgg_view_icon($name, $class = '') {  	// @todo deprecate boolean in Elgg 1.9 -	if (is_bool($class) && $class === true) { +	if ($class === true) {  		$class = 'float';  	}  	return "<span class=\"elgg-icon elgg-icon-$name $class\"></span>"; @@ -1409,7 +1388,8 @@ function elgg_view_access_collections($owner_guid) {   */  function set_template_handler($function_name) {  	global $CONFIG; -	if (!empty($function_name) && is_callable($function_name)) { + +	if (is_callable($function_name)) {  		$CONFIG->template_handler = $function_name;  		return true;  	} @@ -1522,17 +1502,13 @@ function elgg_view_tree($view_root, $viewtype = "") {   * @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 void + * @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 (!isset($i)) { -		$i = 0; -	} -  	if ($handle = opendir($folder)) {  		while ($view = readdir($handle)) {  			if (!in_array($view, array('.', '..', '.svn', 'CVS')) && !is_dir($folder . "/" . $view)) { @@ -1614,16 +1590,15 @@ function elgg_views_handle_deprecated_views() {  function elgg_views_boot() {  	global $CONFIG; -	elgg_register_simplecache_view('css/elgg');  	elgg_register_simplecache_view('css/ie');  	elgg_register_simplecache_view('css/ie6');  	elgg_register_simplecache_view('css/ie7'); -	elgg_register_simplecache_view('js/elgg');  	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'); @@ -1632,14 +1607,17 @@ function elgg_views_boot() {  	elgg_load_js('elgg');  	elgg_register_simplecache_view('js/lightbox'); -	elgg_register_simplecache_view('css/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'); @@ -1653,13 +1631,13 @@ function elgg_views_boot() {  	$views = scandir($view_path);  	foreach ($views as $view) { -		if ('.' !== substr($view, 0, 1) && is_dir($view_path . $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 (!elgg_get_config('icon_sizes')) { +	if (!$CONFIG->icon_sizes) {  		$icon_sizes = array(  			'topbar' => array('w' => 16, 'h' => 16, 'square' => TRUE, 'upscale' => TRUE),  			'tiny' => array('w' => 25, 'h' => 25, 'square' => TRUE, 'upscale' => TRUE), diff --git a/engine/lib/web_services.php b/engine/lib/web_services.php index da3ed76a9..c8e4a13cc 100644 --- a/engine/lib/web_services.php +++ b/engine/lib/web_services.php @@ -232,6 +232,7 @@ function execute_method($method) {  	$function = $API_METHODS[$method]["function"];  	$serialised_parameters = trim($serialised_parameters, ", "); +	// @todo document why we cannot use call_user_func_array here  	$result = eval("return $function($serialised_parameters);");  	// Sanity check result @@ -1194,6 +1195,8 @@ $ERRORS = array();   *   * @return void   * @access private + *  + * @throws Exception   */  function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) {  	global $ERRORS; @@ -1278,11 +1281,9 @@ function service_handler($handler, $request) {  		// 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])) { - +	} else if (isset($CONFIG->servicehandler[$handler]) && is_callable($CONFIG->servicehandler[$handler])) {  		$function = $CONFIG->servicehandler[$handler]; -		$function($request, $handler); +		call_user_func($function, $request, $handler);  	} else {  		// no handler for this web service  		header("HTTP/1.0 404 Not Found"); @@ -1301,10 +1302,11 @@ function service_handler($handler, $request) {   */  function register_service_handler($handler, $function) {  	global $CONFIG; +  	if (!isset($CONFIG->servicehandler)) {  		$CONFIG->servicehandler = array();  	} -	if (is_callable($function)) { +	if (is_callable($function, true)) {  		$CONFIG->servicehandler[$handler] = $function;  		return true;  	} @@ -1319,11 +1321,13 @@ function register_service_handler($handler, $function) {   *   * @param string $handler web services type   * - * @return 1.7.0 + * @return void + * @since 1.7.0   */  function unregister_service_handler($handler) {  	global $CONFIG; -	if (isset($CONFIG->servicehandler) && isset($CONFIG->servicehandler[$handler])) { + +	if (isset($CONFIG->servicehandler, $CONFIG->servicehandler[$handler])) {  		unset($CONFIG->servicehandler[$handler]);  	}  } @@ -1333,6 +1337,8 @@ function unregister_service_handler($handler) {   *   * @return void   * @access private + * + * @throws SecurityException|APIException   */  function rest_handler() {  	global $CONFIG; @@ -1387,7 +1393,7 @@ function rest_handler() {  /**   * Unit tests for API   * - * @param sting  $hook   unit_test + * @param string  $hook   unit_test   * @param string $type   system   * @param mixed  $value  Array of tests   * @param mixed  $params Params @@ -1397,6 +1403,7 @@ function rest_handler() {   */  function api_unit_test($hook, $type, $value, $params) {  	global $CONFIG; +  	$value[] = $CONFIG->path . 'engine/tests/services/api.php';  	return $value;  } @@ -1418,15 +1425,18 @@ function api_init() {  	elgg_echo("system.api.list"), "GET", false, false);  	// The authentication token api -	expose_function("auth.gettoken", -					"auth_gettoken", array( -											'username' => array ('type' => 'string'), -											'password' => array ('type' => 'string'), -											), -					elgg_echo('auth.gettoken'), -					'POST', -					false, -					false); +	expose_function( +		"auth.gettoken", +		"auth_gettoken", +		array( +			'username' => array ('type' => 'string'), +			'password' => array ('type' => 'string'), +		), +		elgg_echo('auth.gettoken'), +		'POST', +		false, +		false +	);  } diff --git a/engine/lib/xml.php b/engine/lib/xml.php index 813bc4ee0..ff82d7e8a 100644 --- a/engine/lib/xml.php +++ b/engine/lib/xml.php @@ -101,47 +101,11 @@ function serialise_array_to_xml(array $data, $n = 0) {  /**   * Parse an XML file into an object. - * Based on code from http://de.php.net/manual/en/function.xml-parse-into-struct.php by - * efredricksen at gmail dot com   *   * @param string $xml The XML   *   * @return object   */  function xml_to_object($xml) { -	$parser = xml_parser_create(); - -	// Parse $xml into a structure -	xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); -	xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); -	xml_parse_into_struct($parser, $xml, $tags); - -	xml_parser_free($parser); - -	$elements = array(); -	$stack = array(); - -	foreach ($tags as $tag) { -		$index = count($elements); - -		if ($tag['type'] == "complete" || $tag['type'] == "open") { -			$elements[$index] = new XmlElement; -			$elements[$index]->name = $tag['tag']; -			$elements[$index]->attributes = elgg_extract('attributes', $tag, ''); -			$elements[$index]->content = elgg_extract('value', $tag, ''); - -			if ($tag['type'] == "open") { -				$elements[$index]->children = array(); -				$stack[count($stack)] = &$elements; -				$elements = &$elements[$index]->children; -			} -		} - -		if ($tag['type'] == "close") { -			$elements = &$stack[count($stack) - 1]; -			unset($stack[count($stack) - 1]); -		} -	} - -	return $elements[0]; +	return new ElggXMLElement($xml);  }  | 
