diff options
Diffstat (limited to 'engine/lib/entities.php')
| -rw-r--r-- | engine/lib/entities.php | 316 | 
1 files changed, 228 insertions, 88 deletions
diff --git a/engine/lib/entities.php b/engine/lib/entities.php index a50567d9f..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. @@ -59,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; @@ -87,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]; @@ -148,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;  }  /** @@ -178,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; +} -		$SUBTYPE_CACHE[$subtype_id] = $result; -		return $result->subtype; +/** + * Retrieve subtype from the cache. + * + * @param string $type + * @param string $subtype + * @return stdClass|null + * + * @access private + */ +function _elgg_retrieve_cached_subtype($type, $subtype) { +	global $SUBTYPE_CACHE; + +	if ($SUBTYPE_CACHE === null) { +		_elgg_populate_subtype_cache();  	} -	return false; +	foreach ($SUBTYPE_CACHE as $obj) { +		if ($obj->type === $type && $obj->subtype === $subtype) { +			return $obj; +		} +	} +	return null; +} + +/** + * 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; +	}  }  /** @@ -225,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;  }  /** @@ -257,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;  }  /** @@ -305,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; @@ -361,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;  }  /** @@ -735,7 +773,13 @@ function get_entity($guid) {  		}  	} -	$new_entity = entity_row_to_elggstar($entity_row); +	// 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);  	} @@ -980,7 +1024,12 @@ 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) {  			// populate entity and metadata caches  			$guids = array(); @@ -1009,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 @@ -1772,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(); @@ -1838,7 +1978,7 @@ function oddentity_to_elggentity(ODDEntity $element) {  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); @@ -2011,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;  	}  | 
