diff options
Diffstat (limited to 'engine')
43 files changed, 1442 insertions, 331 deletions
| diff --git a/engine/classes/ElggEntity.php b/engine/classes/ElggEntity.php index 164ff3838..929abceb2 100644 --- a/engine/classes/ElggEntity.php +++ b/engine/classes/ElggEntity.php @@ -24,7 +24,6 @@   *   * @package    Elgg.Core   * @subpackage DataModel.Entities - * @link       http://docs.elgg.org/DataModel/ElggEntity   *    * @property string $type           object, user, group, or site (read-only after save)   * @property string $subtype        Further clarifies the nature of the entity (read-only after save) @@ -249,7 +248,9 @@ abstract class ElggEntity extends ElggData implements  	 * @return mixed The value, or NULL if not found.  	 */  	public function getMetaData($name) { -		if ((int) ($this->guid) == 0) { +		$guid = $this->getGUID(); + +		if (! $guid) {  			if (isset($this->temp_metadata[$name])) {  				// md is returned as an array only if more than 1 entry  				if (count($this->temp_metadata[$name]) == 1) { @@ -262,21 +263,38 @@ abstract class ElggEntity extends ElggData implements  			}  		} +		// upon first cache miss, just load/cache all the metadata and retry. +		// if this works, the rest of this function may not be needed! +		$cache = elgg_get_metadata_cache(); +		if ($cache->isKnown($guid, $name)) { +			return $cache->load($guid, $name); +		} else { +			$cache->populateFromEntities(array($guid)); +			// in case ignore_access was on, we have to check again... +			if ($cache->isKnown($guid, $name)) { +				return $cache->load($guid, $name); +			} +		} +  		$md = elgg_get_metadata(array( -			'guid' => $this->getGUID(), +			'guid' => $guid,  			'metadata_name' => $name,  			'limit' => 0,  		)); +		$value = null; +  		if ($md && !is_array($md)) { -			return $md->value; +			$value = $md->value;  		} elseif (count($md) == 1) { -			return $md[0]->value; +			$value = $md[0]->value;  		} else if ($md && is_array($md)) { -			return metadata_array_to_values($md); +			$value = metadata_array_to_values($md);  		} -		return null; +		$cache->save($guid, $name, $value); + +		return $value;  	}  	/** @@ -581,7 +599,6 @@ abstract class ElggEntity extends ElggData implements  	 * @param mixed  $value Value of private setting  	 *  	 * @return bool -	 * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings  	 */  	function setPrivateSetting($name, $value) {  		if ((int) $this->guid > 0) { @@ -740,8 +757,6 @@ abstract class ElggEntity extends ElggData implements  	 * @param string $vartype   The type of annotation value  	 *  	 * @return bool -	 * -	 * @link http://docs.elgg.org/DataModel/Annotations  	 */  	function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_id = 0, $vartype = "") {  		if ((int) $this->guid > 0) { @@ -1011,7 +1026,7 @@ abstract class ElggEntity extends ElggData implements  	/**  	 * Returns the guid.  	 * -	 * @return int GUID +	 * @return int|null GUID  	 */  	public function getGUID() {  		return $this->get('guid'); @@ -1249,16 +1264,16 @@ abstract class ElggEntity extends ElggData implements  	/**  	 * Save an entity.  	 * -	 * @return bool/int +	 * @return bool|int  	 * @throws IOException  	 */  	public function save() { -		$guid = (int) $this->guid; +		$guid = $this->getGUID();  		if ($guid > 0) {  			cache_entity($this);  			return update_entity( -				$this->get('guid'), +				$guid,  				$this->get('owner_guid'),  				$this->get('access_id'),  				$this->get('container_guid'), @@ -1305,10 +1320,7 @@ abstract class ElggEntity extends ElggData implements  			$this->attributes['subtype'] = get_subtype_id($this->attributes['type'],  				$this->attributes['subtype']); -			// Cache object handle -			if ($this->attributes['guid']) { -				cache_entity($this); -			} +			cache_entity($this);  			return $this->attributes['guid'];  		} diff --git a/engine/classes/ElggGroup.php b/engine/classes/ElggGroup.php index f7f67bf41..121186196 100644 --- a/engine/classes/ElggGroup.php +++ b/engine/classes/ElggGroup.php @@ -16,8 +16,6 @@ class ElggGroup extends ElggEntity  	 * Sets the type to group.  	 *  	 * @return void -	 * -	 * @deprecated 1.8 Use initializeAttributes  	 */  	protected function initializeAttributes() {  		parent::initializeAttributes(); diff --git a/engine/classes/ElggMemcache.php b/engine/classes/ElggMemcache.php index f27b017d0..d9539b9cb 100644 --- a/engine/classes/ElggMemcache.php +++ b/engine/classes/ElggMemcache.php @@ -40,7 +40,7 @@ class ElggMemcache extends ElggSharedMemoryCache {  		// Do we have memcache?  		if (!class_exists('Memcache')) { -			throw new ConfigurationException(elgg_echo('memcache:notinstalled')); +			throw new ConfigurationException('PHP memcache module not installed, you must install php5-memcache');  		}  		// Create memcache object @@ -48,7 +48,7 @@ class ElggMemcache extends ElggSharedMemoryCache {  		// Now add servers  		if (!$CONFIG->memcache_servers) { -			throw new ConfigurationException(elgg_echo('memcache:noservers')); +			throw new ConfigurationException('No memcache servers defined, please populate the $CONFIG->memcache_servers variable');  		}  		if (is_callable(array($this->memcache, 'addServer'))) { @@ -85,7 +85,7 @@ class ElggMemcache extends ElggSharedMemoryCache {  		// Get version  		$this->version = $this->memcache->getVersion();  		if (version_compare($this->version, ElggMemcache::$MINSERVERVERSION, '<')) { -			$msg = elgg_echo('memcache:versiontoolow', +			$msg = vsprintf('Memcache needs at least version %s to run, you are running %s',  				array(ElggMemcache::$MINSERVERVERSION,  				$this->version  			)); diff --git a/engine/classes/ElggMetadata.php b/engine/classes/ElggMetadata.php index 634a122e5..7f45dc3ea 100644 --- a/engine/classes/ElggMetadata.php +++ b/engine/classes/ElggMetadata.php @@ -26,8 +26,6 @@ class ElggMetadata extends ElggExtender {  	 * Construct a metadata object  	 *  	 * @param mixed $id ID of metadata or a database row as stdClass object -	 * -	 * @return void  	 */  	function __construct($id = null) {  		$this->initializeAttributes(); @@ -54,7 +52,7 @@ class ElggMetadata extends ElggExtender {  	 *  	 * @param int $user_guid The GUID of the user (defaults to currently logged in user)  	 * -	 * @return true|false Depending on permissions +	 * @return bool Depending on permissions  	 */  	function canEdit($user_guid = 0) {  		if ($entity = get_entity($this->get('entity_guid'))) { @@ -64,9 +62,11 @@ class ElggMetadata extends ElggExtender {  	}  	/** -	 * Save matadata object +	 * Save metadata object  	 * -	 * @return int the metadata object id +	 * @return int|bool the metadata object id or true if updated +	 * +	 * @throws IOException  	 */  	function save() {  		if ($this->id > 0) { @@ -89,7 +89,13 @@ class ElggMetadata extends ElggExtender {  	 * @return bool  	 */  	function delete() { -		return elgg_delete_metastring_based_object_by_id($this->id, 'metadata'); +		$success = elgg_delete_metastring_based_object_by_id($this->id, 'metadata'); +		if ($success) { +			// we mark unknown here because this deletes only one value +			// under this name, and there may be others remaining. +			elgg_get_metadata_cache()->markUnknown($this->entity_guid, $this->name); +		} +		return $success;  	}  	/** @@ -99,17 +105,27 @@ class ElggMetadata extends ElggExtender {  	 * @since 1.8  	 */  	function disable() { -		return elgg_set_metastring_based_object_enabled_by_id($this->id, 'no', 'metadata'); +		$success = elgg_set_metastring_based_object_enabled_by_id($this->id, 'no', 'metadata'); +		if ($success) { +			// we mark unknown here because this disables only one value +			// under this name, and there may be others remaining. +			elgg_get_metadata_cache()->markUnknown($this->entity_guid, $this->name); +		} +		return $success;  	}  	/** -	 * Disable the metadata +	 * Enable the metadata  	 *  	 * @return bool  	 * @since 1.8  	 */  	function enable() { -		return elgg_set_metastring_based_object_enabled_by_id($this->id, 'yes', 'metadata'); +		$success = elgg_set_metastring_based_object_enabled_by_id($this->id, 'yes', 'metadata'); +		if ($success) { +			elgg_get_metadata_cache()->markUnknown($this->entity_guid, $this->name); +		} +		return $success;  	}  	/** diff --git a/engine/classes/ElggObject.php b/engine/classes/ElggObject.php index b4bae6825..fa6296c8c 100644 --- a/engine/classes/ElggObject.php +++ b/engine/classes/ElggObject.php @@ -223,7 +223,7 @@ class ElggObject extends ElggEntity {  		// must be member of group  		if (elgg_instanceof($this->getContainerEntity(), 'group')) { -			if (!$this->getContainerEntity()->canWriteToContainer(get_user($user_guid))) { +			if (!$this->getContainerEntity()->canWriteToContainer($user_guid)) {  				return false;  			}  		} diff --git a/engine/classes/ElggPlugin.php b/engine/classes/ElggPlugin.php index 8c9093834..3e43c8e81 100644 --- a/engine/classes/ElggPlugin.php +++ b/engine/classes/ElggPlugin.php @@ -101,7 +101,6 @@ class ElggPlugin extends ElggObject {  			$missing_attributes = array_diff_key($expected_attributes, $row);  			if ($missing_attributes) {  				$needs_loaded = true; -				$old_guid = $guid;  				$guid = $row['guid'];  			} else {  				$this->attributes = $row; @@ -132,10 +131,7 @@ class ElggPlugin extends ElggObject {  		// guid needs to be an int  http://trac.elgg.org/ticket/4111  		$this->attributes['guid'] = (int)$this->attributes['guid']; -		// cache the entity -		if ($this->attributes['guid']) { -			cache_entity($this); -		} +		cache_entity($this);  		return true;  	} diff --git a/engine/classes/ElggPluginManifest.php b/engine/classes/ElggPluginManifest.php index 7aa702d47..a4f5bb95d 100644 --- a/engine/classes/ElggPluginManifest.php +++ b/engine/classes/ElggPluginManifest.php @@ -264,7 +264,7 @@ class ElggPluginManifest {  	/**  	 * Returns the license  	 * -	 * @return sting +	 * @return string  	 */  	public function getLicense() {  		// license vs licence.  Use license. @@ -276,6 +276,32 @@ class ElggPluginManifest {  		}  	} +	/** +	 * Returns the repository url +	 * +	 * @return string +	 */ +	public function getRepositoryURL() { +		return $this->parser->getAttribute('repository'); +	} + +	/** +	 * Returns the bug tracker page +	 * +	 * @return string +	 */ +	public function getBugTrackerURL() { +		return $this->parser->getAttribute('bugtracker'); +	} + +	/** +	 * Returns the donations page +	 * +	 * @return string +	 */ +	public function getDonationsPageURL() { +		return $this->parser->getAttribute('donations'); +	}  	/**  	 * Returns the version of the plugin. @@ -319,12 +345,26 @@ class ElggPluginManifest {  	 * @return array  	 */  	public function getCategories() { +		$bundled_plugins = array('blog', 'bookmarks', 'categories', +			'custom_index', 'dashboard', 'developers', 'diagnostics', +			'embed', 'externalpages', 'file', 'garbagecollector', +			'groups', 'htmlawed', 'invitefriends', 'likes', +			'logbrowser', 'logrotate', 'members', 'messageboard', +			'messages', 'notifications', 'oauth_api', 'pages', 'profile', +			'reportedcontent', 'search', 'tagcloud', 'thewire', 'tinymce', +			'twitter', 'twitter_api', 'uservalidationbyemail', 'zaudio', +		); +  		$cats = $this->parser->getAttribute('category');  		if (!$cats) {  			$cats = array();  		} +		if (in_array('bundled', $cats) && !in_array($this->getPluginID(), $bundled_plugins)) { +			unset($cats[array_search('bundled', $cats)]); +		} +  		return $cats;  	} @@ -442,7 +482,7 @@ class ElggPluginManifest {  	 * Normalizes a dependency array using the defined structs.  	 * Can be used with either requires or suggests.  	 * -	 * @param array $dep An dependency array. +	 * @param array $dep A dependency array.  	 * @return array The normalized deps array.  	 */  	private function normalizeDep($dep) { @@ -486,8 +526,10 @@ class ElggPluginManifest {  							break;  					}  				} -  				break; +			default: +				// unrecognized so we just return the raw dependency +				return $dep;  		}  		$normalized_dep = $this->buildStruct($struct, $dep); diff --git a/engine/classes/ElggPluginManifestParser18.php b/engine/classes/ElggPluginManifestParser18.php index 554e28c02..3b753f17b 100644 --- a/engine/classes/ElggPluginManifestParser18.php +++ b/engine/classes/ElggPluginManifestParser18.php @@ -13,10 +13,10 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser {  	 * @var array  	 */  	protected $validAttributes = array( -		'name', 'author', 'version', 'blurb', 'description', -		'website', 'copyright', 'license', 'requires', 'suggests', -		'screenshot', 'category', 'conflicts', 'provides', -		'activate_on_install' +		'name', 'author', 'version', 'blurb', 'description','website', +		'repository', 'bugtracker', 'donations', 'copyright', 'license', +		'requires', 'suggests', 'conflicts', 'provides', +		'screenshot', 'category', 'activate_on_install'  	);  	/** @@ -46,6 +46,9 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser {  				case 'website':  				case 'copyright':  				case 'license': +				case 'repository': +				case 'bugtracker': +				case 'donations':  				case 'activate_on_install':  					$parsed[$element->name] = $element->content;  					break; diff --git a/engine/classes/ElggSite.php b/engine/classes/ElggSite.php index 6d07778a9..401939005 100644 --- a/engine/classes/ElggSite.php +++ b/engine/classes/ElggSite.php @@ -381,14 +381,24 @@ class ElggSite extends ElggEntity {  	public function checkWalledGarden() {  		global $CONFIG; -		if ($CONFIG->walled_garden && !elgg_is_logged_in()) { -			// hook into the index system call at the highest priority -			elgg_register_plugin_hook_handler('index', 'system', 'elgg_walled_garden_index', 1); - -			if (!$this->isPublicPage()) { -				$_SESSION['last_forward_from'] = current_page_url(); -				register_error(elgg_echo('loggedinrequired')); -				forward(); +		if ($CONFIG->walled_garden) { +			if ($CONFIG->default_access == ACCESS_PUBLIC) { +				$CONFIG->default_access = ACCESS_LOGGED_IN; +			} +			elgg_register_plugin_hook_handler( +					'access:collections:write', +					'user', +					'_elgg_walled_garden_remove_public_access'); + +			if (!elgg_is_logged_in()) { +				// hook into the index system call at the highest priority +				elgg_register_plugin_hook_handler('index', 'system', 'elgg_walled_garden_index', 1); + +				if (!$this->isPublicPage()) { +					$_SESSION['last_forward_from'] = current_page_url(); +					register_error(elgg_echo('loggedinrequired')); +					forward(); +				}  			}  		}  	} @@ -423,6 +433,7 @@ class ElggSite extends ElggEntity {  		// default public pages  		$defaults = array(  			'walled_garden/.*', +			'login',  			'action/login',  			'register',  			'action/register', diff --git a/engine/classes/ElggTranslit.php b/engine/classes/ElggTranslit.php new file mode 100644 index 000000000..676c59fc8 --- /dev/null +++ b/engine/classes/ElggTranslit.php @@ -0,0 +1,262 @@ +<?php +/** + * Elgg Transliterate + * + * For creating "friendly titles" for URLs + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. For more information, see + * <http://www.doctrine-project.org>. + * + * @author      Konsta Vesterinen <kvesteri@cc.hut.fi> + * @author      Jonathan H. Wage <jonwage@gmail.com> + * + * @author      Steve Clay <steve@mrclay.org> + * @package     Elgg.Core + * + * @access private Plugin authors should not use this directly + */ +class ElggTranslit { + +	/** +	 * Create a version of a string for embedding in a URL +	 * @param string $string a UTF-8 string +	 * @param string $separator +	 * @return string +	 */ +	static public function urlize($string, $separator = '-') { +		// Iñtërnâtiônàlizætiøn, AND 日本語! + +		// try to force combined chars because the translit map and others expect it +		if (self::hasNormalizerSupport()) { +			$nfc = normalizer_normalize($string); +			if (is_string($nfc)) { +				$string = $nfc; +			} +		} +		// Internationalization, AND 日本語! +		$string = self::transliterateAscii($string); + +		// more translation +		$string = strtr($string, array( +			// Euro/GBP +			"\xE2\x82\xAC" /* € */ => 'E', "\xC2\xA3" /* £ */ => 'GBP', +		)); + +		// remove all ASCII except 0-9a-zA-Z, hyphen, underscore, and whitespace +		// note: "x" modifier did not work with this pattern. +		$string = preg_replace('~[' +			. '\x00-\x08'  # control chars +			. '\x0b\x0c'   # vert tab, form feed +			. '\x0e-\x1f'  # control chars +			. '\x21-\x2c'  # ! ... , +			. '\x2e\x2f'   # . slash +			. '\x3a-\x40'  # : ... @ +			. '\x5b-\x5e'  # [ ... ^ +			. '\x60'       # ` +			. '\x7b-\x7f'  # { ... DEL +			. ']~', '', $string); +		$string = strtr($string, '', ''); + +		// internationalization, and 日本語! +		// note: not using elgg_strtolower to keep this class portable +		$string = is_callable('mb_strtolower') +			? mb_strtolower($string, 'UTF-8') +			: strtolower($string); + +		// split by ASCII chars not in 0-9a-zA-Z +		// note: we cannot use [^0-9a-zA-Z] because that matches multibyte chars. +		// note: "x" modifier did not work with this pattern. +		$pattern = '~[' +			. '\x00-\x2f'  # controls ... slash +			. '\x3a-\x40'  # : ... @ +			. '\x5b-\x60'  # [ ... ` +			. '\x7b-\x7f'  # { ... DEL +			. ']+~x'; + +		// ['internationalization', 'and', '日本語'] +		$words = preg_split($pattern, $string, -1, PREG_SPLIT_NO_EMPTY); + +		// ['internationalization', 'and', '%E6%97%A5%E6%9C%AC%E8%AA%9E'] +		$words = array_map('urlencode', $words); + +		// internationalization-and-%E6%97%A5%E6%9C%AC%E8%AA%9E +		return implode($separator, $words); +	} + +	/** +	 * Transliterate Western multibyte chars to ASCII +	 * @param string $utf8 a UTF-8 string +	 * @return string +	 */ +	static public function transliterateAscii($utf8) { +		static $map = null; +		if (!preg_match('/[\x80-\xff]/', $utf8)) { +			return $utf8; +		} +		if (null === $map) { +			$map = self::getAsciiTranslitMap(); +		} +		return strtr($utf8, $map); +	} + +	/** +	 * Get array of UTF-8 (NFC) character replacements. +	 * +	 * @return array +	 */ +	static public function getAsciiTranslitMap() { +		return array( +			// Decompositions for Latin-1 Supplement +			"\xC2\xAA" /* ª */ => 'a', "\xC2\xBA" /* º */ => 'o', "\xC3\x80" /* À */ => 'A', +			"\xC3\x81" /* Á */ => 'A', "\xC3\x82" /*  */ => 'A', "\xC3\x83" /* à */ => 'A', +			"\xC3\x84" /* Ä */ => 'A', "\xC3\x85" /* Å */ => 'A', "\xC3\x86" /* Æ */ => 'AE', +			"\xC3\x87" /* Ç */ => 'C', "\xC3\x88" /* È */ => 'E', "\xC3\x89" /* É */ => 'E', +			"\xC3\x8A" /* Ê */ => 'E', "\xC3\x8B" /* Ë */ => 'E', "\xC3\x8C" /* Ì */ => 'I', +			"\xC3\x8D" /* Í */ => 'I', "\xC3\x8E" /* Î */ => 'I', "\xC3\x8F" /* Ï */ => 'I', +			"\xC3\x90" /* Ð */ => 'D', "\xC3\x91" /* Ñ */ => 'N', "\xC3\x92" /* Ò */ => 'O', +			"\xC3\x93" /* Ó */ => 'O', "\xC3\x94" /* Ô */ => 'O', "\xC3\x95" /* Õ */ => 'O', +			"\xC3\x96" /* Ö */ => 'O', "\xC3\x99" /* Ù */ => 'U', "\xC3\x9A" /* Ú */ => 'U', +			"\xC3\x9B" /* Û */ => 'U', "\xC3\x9C" /* Ü */ => 'U', "\xC3\x9D" /* Ý */ => 'Y', +			"\xC3\x9E" /* Þ */ => 'TH', "\xC3\x9F" /* ß */ => 'ss', "\xC3\xA0" /* à */ => 'a', +			"\xC3\xA1" /* á */ => 'a', "\xC3\xA2" /* â */ => 'a', "\xC3\xA3" /* ã */ => 'a', +			"\xC3\xA4" /* ä */ => 'a', "\xC3\xA5" /* å */ => 'a', "\xC3\xA6" /* æ */ => 'ae', +			"\xC3\xA7" /* ç */ => 'c', "\xC3\xA8" /* è */ => 'e', "\xC3\xA9" /* é */ => 'e', +			"\xC3\xAA" /* ê */ => 'e', "\xC3\xAB" /* ë */ => 'e', "\xC3\xAC" /* ì */ => 'i', +			"\xC3\xAD" /* í */ => 'i', "\xC3\xAE" /* î */ => 'i', "\xC3\xAF" /* ï */ => 'i', +			"\xC3\xB0" /* ð */ => 'd', "\xC3\xB1" /* ñ */ => 'n', "\xC3\xB2" /* ò */ => 'o', +			"\xC3\xB3" /* ó */ => 'o', "\xC3\xB4" /* ô */ => 'o', "\xC3\xB5" /* õ */ => 'o', +			"\xC3\xB6" /* ö */ => 'o', "\xC3\xB8" /* ø */ => 'o', "\xC3\xB9" /* ù */ => 'u', +			"\xC3\xBA" /* ú */ => 'u', "\xC3\xBB" /* û */ => 'u', "\xC3\xBC" /* ü */ => 'u', +			"\xC3\xBD" /* ý */ => 'y', "\xC3\xBE" /* þ */ => 'th', "\xC3\xBF" /* ÿ */ => 'y', +			"\xC3\x98" /* Ø */ => 'O', +			// Decompositions for Latin Extended-A +			"\xC4\x80" /* Ā */ => 'A', "\xC4\x81" /* ā */ => 'a', "\xC4\x82" /* Ă */ => 'A', +			"\xC4\x83" /* ă */ => 'a', "\xC4\x84" /* Ą */ => 'A', "\xC4\x85" /* ą */ => 'a', +			"\xC4\x86" /* Ć */ => 'C', "\xC4\x87" /* ć */ => 'c', "\xC4\x88" /* Ĉ */ => 'C', +			"\xC4\x89" /* ĉ */ => 'c', "\xC4\x8A" /* Ċ */ => 'C', "\xC4\x8B" /* ċ */ => 'c', +			"\xC4\x8C" /* Č */ => 'C', "\xC4\x8D" /* č */ => 'c', "\xC4\x8E" /* Ď */ => 'D', +			"\xC4\x8F" /* ď */ => 'd', "\xC4\x90" /* Đ */ => 'D', "\xC4\x91" /* đ */ => 'd', +			"\xC4\x92" /* Ē */ => 'E', "\xC4\x93" /* ē */ => 'e', "\xC4\x94" /* Ĕ */ => 'E', +			"\xC4\x95" /* ĕ */ => 'e', "\xC4\x96" /* Ė */ => 'E', "\xC4\x97" /* ė */ => 'e', +			"\xC4\x98" /* Ę */ => 'E', "\xC4\x99" /* ę */ => 'e', "\xC4\x9A" /* Ě */ => 'E', +			"\xC4\x9B" /* ě */ => 'e', "\xC4\x9C" /* Ĝ */ => 'G', "\xC4\x9D" /* ĝ */ => 'g', +			"\xC4\x9E" /* Ğ */ => 'G', "\xC4\x9F" /* ğ */ => 'g', "\xC4\xA0" /* Ġ */ => 'G', +			"\xC4\xA1" /* ġ */ => 'g', "\xC4\xA2" /* Ģ */ => 'G', "\xC4\xA3" /* ģ */ => 'g', +			"\xC4\xA4" /* Ĥ */ => 'H', "\xC4\xA5" /* ĥ */ => 'h', "\xC4\xA6" /* Ħ */ => 'H', +			"\xC4\xA7" /* ħ */ => 'h', "\xC4\xA8" /* Ĩ */ => 'I', "\xC4\xA9" /* ĩ */ => 'i', +			"\xC4\xAA" /* Ī */ => 'I', "\xC4\xAB" /* ī */ => 'i', "\xC4\xAC" /* Ĭ */ => 'I', +			"\xC4\xAD" /* ĭ */ => 'i', "\xC4\xAE" /* Į */ => 'I', "\xC4\xAF" /* į */ => 'i', +			"\xC4\xB0" /* İ */ => 'I', "\xC4\xB1" /* ı */ => 'i', "\xC4\xB2" /* IJ */ => 'IJ', +			"\xC4\xB3" /* ij */ => 'ij', "\xC4\xB4" /* Ĵ */ => 'J', "\xC4\xB5" /* ĵ */ => 'j', +			"\xC4\xB6" /* Ķ */ => 'K', "\xC4\xB7" /* ķ */ => 'k', "\xC4\xB8" /* ĸ */ => 'k', +			"\xC4\xB9" /* Ĺ */ => 'L', "\xC4\xBA" /* ĺ */ => 'l', "\xC4\xBB" /* Ļ */ => 'L', +			"\xC4\xBC" /* ļ */ => 'l', "\xC4\xBD" /* Ľ */ => 'L', "\xC4\xBE" /* ľ */ => 'l', +			"\xC4\xBF" /* Ŀ */ => 'L', "\xC5\x80" /* ŀ */ => 'l', "\xC5\x81" /* Ł */ => 'L', +			"\xC5\x82" /* ł */ => 'l', "\xC5\x83" /* Ń */ => 'N', "\xC5\x84" /* ń */ => 'n', +			"\xC5\x85" /* Ņ */ => 'N', "\xC5\x86" /* ņ */ => 'n', "\xC5\x87" /* Ň */ => 'N', +			"\xC5\x88" /* ň */ => 'n', "\xC5\x89" /* ʼn */ => 'N', "\xC5\x8A" /* Ŋ */ => 'n', +			"\xC5\x8B" /* ŋ */ => 'N', "\xC5\x8C" /* Ō */ => 'O', "\xC5\x8D" /* ō */ => 'o', +			"\xC5\x8E" /* Ŏ */ => 'O', "\xC5\x8F" /* ŏ */ => 'o', "\xC5\x90" /* Ő */ => 'O', +			"\xC5\x91" /* ő */ => 'o', "\xC5\x92" /* Œ */ => 'OE', "\xC5\x93" /* œ */ => 'oe', +			"\xC5\x94" /* Ŕ */ => 'R', "\xC5\x95" /* ŕ */ => 'r', "\xC5\x96" /* Ŗ */ => 'R', +			"\xC5\x97" /* ŗ */ => 'r', "\xC5\x98" /* Ř */ => 'R', "\xC5\x99" /* ř */ => 'r', +			"\xC5\x9A" /* Ś */ => 'S', "\xC5\x9B" /* ś */ => 's', "\xC5\x9C" /* Ŝ */ => 'S', +			"\xC5\x9D" /* ŝ */ => 's', "\xC5\x9E" /* Ş */ => 'S', "\xC5\x9F" /* ş */ => 's', +			"\xC5\xA0" /* Š */ => 'S', "\xC5\xA1" /* š */ => 's', "\xC5\xA2" /* Ţ */ => 'T', +			"\xC5\xA3" /* ţ */ => 't', "\xC5\xA4" /* Ť */ => 'T', "\xC5\xA5" /* ť */ => 't', +			"\xC5\xA6" /* Ŧ */ => 'T', "\xC5\xA7" /* ŧ */ => 't', "\xC5\xA8" /* Ũ */ => 'U', +			"\xC5\xA9" /* ũ */ => 'u', "\xC5\xAA" /* Ū */ => 'U', "\xC5\xAB" /* ū */ => 'u', +			"\xC5\xAC" /* Ŭ */ => 'U', "\xC5\xAD" /* ŭ */ => 'u', "\xC5\xAE" /* Ů */ => 'U', +			"\xC5\xAF" /* ů */ => 'u', "\xC5\xB0" /* Ű */ => 'U', "\xC5\xB1" /* ű */ => 'u', +			"\xC5\xB2" /* Ų */ => 'U', "\xC5\xB3" /* ų */ => 'u', "\xC5\xB4" /* Ŵ */ => 'W', +			"\xC5\xB5" /* ŵ */ => 'w', "\xC5\xB6" /* Ŷ */ => 'Y', "\xC5\xB7" /* ŷ */ => 'y', +			"\xC5\xB8" /* Ÿ */ => 'Y', "\xC5\xB9" /* Ź */ => 'Z', "\xC5\xBA" /* ź */ => 'z', +			"\xC5\xBB" /* Ż */ => 'Z', "\xC5\xBC" /* ż */ => 'z', "\xC5\xBD" /* Ž */ => 'Z', +			"\xC5\xBE" /* ž */ => 'z', "\xC5\xBF" /* ſ */ => 's', +			// Decompositions for Latin Extended-B +			"\xC8\x98" /* Ș */ => 'S', "\xC8\x99" /* ș */ => 's', +			"\xC8\x9A" /* Ț */ => 'T', "\xC8\x9B" /* ț */ => 't', +			// unmarked +			"\xC6\xA0" /* Ơ */ => 'O', "\xC6\xA1" /* ơ */ => 'o', +			"\xC6\xAF" /* Ư */ => 'U', "\xC6\xB0" /* ư */ => 'u', +			// grave accent +			"\xE1\xBA\xA6" /* Ầ */ => 'A', "\xE1\xBA\xA7" /* ầ */ => 'a', +			"\xE1\xBA\xB0" /* Ằ */ => 'A', "\xE1\xBA\xB1" /* ằ */ => 'a', +			"\xE1\xBB\x80" /* Ề */ => 'E', "\xE1\xBB\x81" /* ề */ => 'e', +			"\xE1\xBB\x92" /* Ồ */ => 'O', "\xE1\xBB\x93" /* ồ */ => 'o', +			"\xE1\xBB\x9C" /* Ờ */ => 'O', "\xE1\xBB\x9D" /* ờ */ => 'o', +			"\xE1\xBB\xAA" /* Ừ */ => 'U', "\xE1\xBB\xAB" /* ừ */ => 'u', +			"\xE1\xBB\xB2" /* Ỳ */ => 'Y', "\xE1\xBB\xB3" /* ỳ */ => 'y', +			// hook +			"\xE1\xBA\xA2" /* Ả */ => 'A', "\xE1\xBA\xA3" /* ả */ => 'a', +			"\xE1\xBA\xA8" /* Ẩ */ => 'A', "\xE1\xBA\xA9" /* ẩ */ => 'a', +			"\xE1\xBA\xB2" /* Ẳ */ => 'A', "\xE1\xBA\xB3" /* ẳ */ => 'a', +			"\xE1\xBA\xBA" /* Ẻ */ => 'E', "\xE1\xBA\xBB" /* ẻ */ => 'e', +			"\xE1\xBB\x82" /* Ể */ => 'E', "\xE1\xBB\x83" /* ể */ => 'e', +			"\xE1\xBB\x88" /* Ỉ */ => 'I', "\xE1\xBB\x89" /* ỉ */ => 'i', +			"\xE1\xBB\x8E" /* Ỏ */ => 'O', "\xE1\xBB\x8F" /* ỏ */ => 'o', +			"\xE1\xBB\x94" /* Ổ */ => 'O', "\xE1\xBB\x95" /* ổ */ => 'o', +			"\xE1\xBB\x9E" /* Ở */ => 'O', "\xE1\xBB\x9F" /* ở */ => 'o', +			"\xE1\xBB\xA6" /* Ủ */ => 'U', "\xE1\xBB\xA7" /* ủ */ => 'u', +			"\xE1\xBB\xAC" /* Ử */ => 'U', "\xE1\xBB\xAD" /* ử */ => 'u', +			"\xE1\xBB\xB6" /* Ỷ */ => 'Y', "\xE1\xBB\xB7" /* ỷ */ => 'y', +			// tilde +			"\xE1\xBA\xAA" /* Ẫ */ => 'A', "\xE1\xBA\xAB" /* ẫ */ => 'a', +			"\xE1\xBA\xB4" /* Ẵ */ => 'A', "\xE1\xBA\xB5" /* ẵ */ => 'a', +			"\xE1\xBA\xBC" /* Ẽ */ => 'E', "\xE1\xBA\xBD" /* ẽ */ => 'e', +			"\xE1\xBB\x84" /* Ễ */ => 'E', "\xE1\xBB\x85" /* ễ */ => 'e', +			"\xE1\xBB\x96" /* Ỗ */ => 'O', "\xE1\xBB\x97" /* ỗ */ => 'o', +			"\xE1\xBB\xA0" /* Ỡ */ => 'O', "\xE1\xBB\xA1" /* ỡ */ => 'o', +			"\xE1\xBB\xAE" /* Ữ */ => 'U', "\xE1\xBB\xAF" /* ữ */ => 'u', +			"\xE1\xBB\xB8" /* Ỹ */ => 'Y', "\xE1\xBB\xB9" /* ỹ */ => 'y', +			// acute accent +			"\xE1\xBA\xA4" /* Ấ */ => 'A', "\xE1\xBA\xA5" /* ấ */ => 'a', +			"\xE1\xBA\xAE" /* Ắ */ => 'A', "\xE1\xBA\xAF" /* ắ */ => 'a', +			"\xE1\xBA\xBE" /* Ế */ => 'E', "\xE1\xBA\xBF" /* ế */ => 'e', +			"\xE1\xBB\x90" /* Ố */ => 'O', "\xE1\xBB\x91" /* ố */ => 'o', +			"\xE1\xBB\x9A" /* Ớ */ => 'O', "\xE1\xBB\x9B" /* ớ */ => 'o', +			"\xE1\xBB\xA8" /* Ứ */ => 'U', "\xE1\xBB\xA9" /* ứ */ => 'u', +			// dot below +			"\xE1\xBA\xA0" /* Ạ */ => 'A', "\xE1\xBA\xA1" /* ạ */ => 'a', +			"\xE1\xBA\xAC" /* Ậ */ => 'A', "\xE1\xBA\xAD" /* ậ */ => 'a', +			"\xE1\xBA\xB6" /* Ặ */ => 'A', "\xE1\xBA\xB7" /* ặ */ => 'a', +			"\xE1\xBA\xB8" /* Ẹ */ => 'E', "\xE1\xBA\xB9" /* ẹ */ => 'e', +			"\xE1\xBB\x86" /* Ệ */ => 'E', "\xE1\xBB\x87" /* ệ */ => 'e', +			"\xE1\xBB\x8A" /* Ị */ => 'I', "\xE1\xBB\x8B" /* ị */ => 'i', +			"\xE1\xBB\x8C" /* Ọ */ => 'O', "\xE1\xBB\x8D" /* ọ */ => 'o', +			"\xE1\xBB\x98" /* Ộ */ => 'O', "\xE1\xBB\x99" /* ộ */ => 'o', +			"\xE1\xBB\xA2" /* Ợ */ => 'O', "\xE1\xBB\xA3" /* ợ */ => 'o', +			"\xE1\xBB\xA4" /* Ụ */ => 'U', "\xE1\xBB\xA5" /* ụ */ => 'u', +			"\xE1\xBB\xB0" /* Ự */ => 'U', "\xE1\xBB\xB1" /* ự */ => 'u', +			"\xE1\xBB\xB4" /* Ỵ */ => 'Y', "\xE1\xBB\xB5" /* ỵ */ => 'y', +		); +	} + +	/** +	 * Tests that "normalizer_normalize" exists and works +	 * @return bool +	 */ +	static public function hasNormalizerSupport() { +		static $ret = null; +		if (null === $ret) { +			$form_c = "\xC3\x85"; // 'LATIN CAPITAL LETTER A WITH RING ABOVE' (U+00C5) +			$form_d = "A\xCC\x8A"; // A followed by 'COMBINING RING ABOVE' (U+030A) +			$ret = (function_exists('normalizer_normalize') +				    && $form_c === normalizer_normalize($form_d)); +		} +		return $ret; +	} +} diff --git a/engine/classes/ElggVolatileMetadataCache.php b/engine/classes/ElggVolatileMetadataCache.php new file mode 100644 index 000000000..24ae58d42 --- /dev/null +++ b/engine/classes/ElggVolatileMetadataCache.php @@ -0,0 +1,344 @@ +<?php +/** + * ElggVolatileMetadataCache + * In memory cache of known metadata values stored by entity. + * + * @package    Elgg.Core + * @subpackage Cache + * + * @access private + */ +class ElggVolatileMetadataCache { + +	/** +	 * The cached values (or null for known to be empty). If the portion of the cache +	 * is synchronized, missing values are assumed to indicate that values do not +	 * exist in storage, otherwise, we don't know what's there. +	 * +	 * @var array +	 */ +	protected $values = array(); + +	/** +	 * Does the cache know that it contains all names fetch-able from storage? +	 * The keys are entity GUIDs and either the value exists (true) or it's not set. +	 * +	 * @var array +	 */ +	protected $isSynchronized = array(); + +	/** +	 * @var null|bool +	 */ +	protected $ignoreAccess = null; + +	/** +	 * @param int $entity_guid +	 * +	 * @param array $values +	 */ +	public function saveAll($entity_guid, array $values) { +		if (!$this->getIgnoreAccess()) { +			$this->values[$entity_guid] = $values; +			$this->isSynchronized[$entity_guid] = true; +		} +	} + +	/** +	 * @param int $entity_guid +	 * +	 * @return array +	 */ +	public function loadAll($entity_guid) { +		if (isset($this->values[$entity_guid])) { +			return $this->values[$entity_guid]; +		} else { +			return array(); +		} +	} + +	/** +	 * Declare that there may be fetch-able metadata names in storage that this +	 * cache doesn't know about +	 * +	 * @param int $entity_guid +	 */ +	public function markOutOfSync($entity_guid) { +		unset($this->isSynchronized[$entity_guid]); +	} + +	/** +	 * @param $entity_guid +	 * +	 * @return bool +	 */ +	public function isSynchronized($entity_guid) { +		return isset($this->isSynchronized[$entity_guid]); +	} + +	/** +	 * @param int $entity_guid +	 * +	 * @param string $name +	 * +	 * @param array|int|string|null $value  null means it is known that there is no +	 *                                      fetch-able metadata under this name +	 * @param bool $allow_multiple +	 */ +	public function save($entity_guid, $name, $value, $allow_multiple = false) { +		if ($this->getIgnoreAccess()) { +			// we don't know if what gets saves here will be available to user once +			// access control returns, hence it's best to forget :/ +			$this->markUnknown($entity_guid, $name); +		} else { +			if ($allow_multiple) { +				if ($this->isKnown($entity_guid, $name)) { +					$existing = $this->load($entity_guid, $name); +					if ($existing !== null) { +						$existing = (array) $existing; +						$existing[] = $value; +						$value = $existing; +					} +				} else { +					// we don't know whether there are unknown values, so it's +					// safest to leave that assumption +					$this->markUnknown($entity_guid, $name); +					return; +				} +			} +			$this->values[$entity_guid][$name] = $value; +		} +	} + +	/** +	 * Warning: You should always call isKnown() beforehand to verify that this +	 * function's return value should be trusted (otherwise a null return value +	 * is ambiguous). +	 * +	 * @param int $entity_guid +	 * +	 * @param string $name +	 * +	 * @return array|string|int|null null = value does not exist +	 */ +	public function load($entity_guid, $name) { +		if (isset($this->values[$entity_guid]) && array_key_exists($name, $this->values[$entity_guid])) { +			return $this->values[$entity_guid][$name]; +		} else { +			return null; +		} +	} + +	/** +	 * Forget about this metadata entry. We don't want to try to guess what the +	 * next fetch from storage will return +	 * +	 * @param int $entity_guid +	 * +	 * @param string $name +	 */ +	public function markUnknown($entity_guid, $name) { +		unset($this->values[$entity_guid][$name]); +		$this->markOutOfSync($entity_guid); +	} + +	/** +	 * If true, load() will return an accurate value for this name +	 * +	 * @param int $entity_guid +	 * +	 * @param string $name +	 * +	 * @return bool +	 */ +	public function isKnown($entity_guid, $name) { +		if (isset($this->isSynchronized[$entity_guid])) { +			return true; +		} else { +			return (isset($this->values[$entity_guid]) && array_key_exists($name, $this->values[$entity_guid])); +		} + +	} + +	/** +	 * Declare that metadata under this name is known to be not fetch-able from storage +	 * +	 * @param int $entity_guid +	 * +	 * @param string $name +	 * +	 * @return array +	 */ +	public function markEmpty($entity_guid, $name) { +		$this->values[$entity_guid][$name] = null; +	} + +	/** +	 * Forget about all metadata for an entity +	 * +	 * @param int $entity_guid +	 */ +	public function clear($entity_guid) { +		$this->values[$entity_guid] = array(); +		$this->markOutOfSync($entity_guid); +	} + +	/** +	 * Clear entire cache and mark all entities as out of sync +	 */ +	public function flush() { +		$this->values = array(); +		$this->isSynchronized = array(); +	} + +	/** +	 * Use this value instead of calling elgg_get_ignore_access(). By default that +	 * function will be called. +	 * +	 * This setting makes this component a little more loosely-coupled. +	 * +	 * @param bool $ignore +	 */ +	public function setIgnoreAccess($ignore) { +		$this->ignoreAccess = (bool) $ignore; +	} + +	/** +	 * Tell the cache to call elgg_get_ignore_access() to determing access status. +	 */ +	public function unsetIgnoreAccess() { +		$this->ignoreAccess = null; +	} + +	/** +	 * @return bool +	 */ +	protected function getIgnoreAccess() { +		if (null === $this->ignoreAccess) { +			return elgg_get_ignore_access(); +		} else { +			return $this->ignoreAccess; +		} +	} + +	/** +	 * Invalidate based on options passed to the global *_metadata functions +	 * +	 * @param string $action  Action performed on metadata. "delete", "disable", or "enable" +	 * +	 * @param array $options  Options passed to elgg_(delete|disable|enable)_metadata +	 * +	 *   "guid" if given, invalidation will be limited to this entity +	 * +	 *   "metadata_name" if given, invalidation will be limited to metadata with this name +	 */ +	public function invalidateByOptions($action, array $options) { +		// remove as little as possible, optimizing for common cases +		if (empty($options['guid'])) { +			// safest to clear everything unless we want to make this even more complex :( +			$this->flush(); +		} else { +			if (empty($options['metadata_name'])) { +				// safest to clear the whole entity +				$this->clear($options['guid']); +			} else { +				switch ($action) { +					case 'delete': +						$this->markEmpty($options['guid'], $options['metadata_name']); +						break; +					default: +						$this->markUnknown($options['guid'], $options['metadata_name']); +				} +			} +		} +	} + +	/** +	 * @param int|array $guids +	 */ +	public function populateFromEntities($guids) { +		if (empty($guids)) { +			return; +		} +		if (!is_array($guids)) { +			$guids = array($guids); +		} +		$guids = array_unique($guids); + +		// could be useful at some point in future +		//$guids = $this->filterMetadataHeavyEntities($guids); + +		$db_prefix = elgg_get_config('dbprefix'); +		$options = array( +			'guids' => $guids, +			'limit' => 0, +			'callback' => false, +			'joins' => array( +				"JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id", +				"JOIN {$db_prefix}metastrings n ON n_table.name_id = n.id", +			), +			'selects' => array('n.string AS name', 'v.string AS value'), +			'order_by' => 'n_table.entity_guid, n_table.time_created ASC', +		); +		$data = elgg_get_metadata($options); + +		// build up metadata for each entity, save when GUID changes (or data ends) +		$last_guid = null; +		$metadata = array(); +		$last_row_idx = count($data) - 1; +		foreach ($data as $i => $row) { +			$name = $row->name; +			$value = ($row->value_type === 'text') ? $row->value : (int) $row->value; +			$guid = $row->entity_guid; +			if ($guid !== $last_guid) { +				if ($last_guid) { +					$this->saveAll($last_guid, $metadata); +				} +				$metadata = array(); +			} +			if (isset($metadata[$name])) { +				$metadata[$name] = (array) $metadata[$name]; +				$metadata[$name][] = $value; +			} else { +				$metadata[$name] = $value; +			} +			if (($i == $last_row_idx)) { +				$this->saveAll($guid, $metadata); +			} +			$last_guid = $guid; +		} +	} + +	/** +	 * Filter out entities whose concatenated metadata values (INTs casted as string) +	 * exceed a threshold in characters. This could be used to avoid overpopulating the +	 * cache if RAM usage becomes an issue. +	 * +	 * @param array $guids GUIDs of entities to examine +	 * +	 * @param int $limit Limit in characters of all metadata (with ints casted to strings) +	 * +	 * @return array +	 */ +	public function filterMetadataHeavyEntities(array $guids, $limit = 1024000) { +		$db_prefix = elgg_get_config('dbprefix'); + +		$options = array( +			'guids' => $guids, +			'limit' => 0, +			'callback' => false, +			'joins' => "JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id", +			'selects' => array('SUM(LENGTH(v.string)) AS bytes'), +			'order_by' => 'n_table.entity_guid, n_table.time_created ASC', +			'group_by' => 'n_table.entity_guid', +		); +		$data = elgg_get_metadata($options); +		// don't cache if metadata for entity is over 10MB (or rolled INT) +		foreach ($data as $row) { +			if ($row->bytes > $limit || $row->bytes < 0) { +				array_splice($guids, array_search($row->entity_guid, $guids), 1); +			} +		} +		return $guids; +	} +} diff --git a/engine/lib/actions.php b/engine/lib/actions.php index c6613e6d6..53b185dea 100644 --- a/engine/lib/actions.php +++ b/engine/lib/actions.php @@ -82,44 +82,28 @@ function action($action, $forwarder = "") {  	$forwarder = str_replace(elgg_get_site_url(), "", $forwarder);  	$forwarder = str_replace("http://", "", $forwarder);  	$forwarder = str_replace("@", "", $forwarder); -  	if (substr($forwarder, 0, 1) == "/") {  		$forwarder = substr($forwarder, 1);  	} -	if (isset($CONFIG->actions[$action])) { -		if (elgg_is_admin_logged_in() || ($CONFIG->actions[$action]['access'] !== 'admin')) { -			if (elgg_is_logged_in() || ($CONFIG->actions[$action]['access'] === 'public')) { - -				// Trigger action event -				// @todo This is only called before the primary action is called. -				$event_result = true; -				$event_result = elgg_trigger_plugin_hook('action', $action, null, $event_result); - -				// Include action -				// Event_result being false doesn't produce an error -				// since i assume this will be handled in the hook itself. -				// @todo make this better! -				if ($event_result) { -					if (!include($CONFIG->actions[$action]['file'])) { -						register_error(elgg_echo('actionnotfound', array($action))); -					} -				} -			} else { -				register_error(elgg_echo('actionloggedout')); +	if (!isset($CONFIG->actions[$action])) { +		register_error(elgg_echo('actionundefined', array($action))); +	} elseif (!elgg_is_admin_logged_in() && ($CONFIG->actions[$action]['access'] === 'admin')) { +		register_error(elgg_echo('actionunauthorized')); +	} elseif (!elgg_is_logged_in() && ($CONFIG->actions[$action]['access'] !== 'public')) { +		register_error(elgg_echo('actionloggedout')); +	} else { +		// Returning falsy doesn't produce an error +		// We assume this will be handled in the hook itself. +		if (elgg_trigger_plugin_hook('action', $action, null, true)) { +			if (!include($CONFIG->actions[$action]['file'])) { +				register_error(elgg_echo('actionnotfound', array($action)));  			} -		} else { -			register_error(elgg_echo('actionunauthorized'));  		} -	} else { -		register_error(elgg_echo('actionundefined', array($action)));  	} -	if (!empty($forwarder)) { -		forward($forwarder); -	} else { -		forward(REFERER); -	} +	$forwarder = empty($forwarder) ? REFERER : $forwarder; +	forward($forwarder);  }  /** @@ -273,8 +257,19 @@ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL)  		} else if ($visibleerrors) {  			register_error(elgg_echo('actiongatekeeper:tokeninvalid'));  		} -	} else if ($visibleerrors) { -		register_error(elgg_echo('actiongatekeeper:missingfields')); +	} else { +		if (! empty($_SERVER['CONTENT_LENGTH']) && empty($_POST)) { +			// The size of $_POST or uploaded file has exceed the size limit +			$error_msg = elgg_trigger_plugin_hook('action_gatekeeper:upload_exceeded_msg', 'all', array( +				'post_size' => $_SERVER['CONTENT_LENGTH'], +				'visible_errors' => $visibleerrors, +			), elgg_echo('actiongatekeeper:uploadexceeded')); +		} else { +			$error_msg = elgg_echo('actiongatekeeper:missingfields'); +		} +		if ($visibleerrors) { +			register_error($error_msg); +		}  	}  	return FALSE; diff --git a/engine/lib/admin.php b/engine/lib/admin.php index 928101fc5..b65d98c95 100644 --- a/engine/lib/admin.php +++ b/engine/lib/admin.php @@ -244,6 +244,7 @@ function admin_init() {  	elgg_register_action('profile/fields/delete', '', 'admin');  	elgg_register_action('profile/fields/reorder', '', 'admin'); +	elgg_register_simplecache_view('css/admin');  	elgg_register_simplecache_view('js/admin');  	$url = elgg_get_simplecache_url('js', 'admin');  	elgg_register_js('elgg.admin', $url); @@ -571,7 +572,7 @@ function admin_markdown_page_handler($pages) {  	if (!$plugin) {  		$error = elgg_echo('admin:plugins:markdown:unknown_plugin');  		$body = elgg_view_layout('admin', array('content' => $error, 'title' => $error)); -		echo elgg_view_page($title, $body, 'admin'); +		echo elgg_view_page($error, $body, 'admin');  		return true;  	} diff --git a/engine/lib/cache.php b/engine/lib/cache.php index c117b9ec9..be1c43e14 100644 --- a/engine/lib/cache.php +++ b/engine/lib/cache.php @@ -442,6 +442,7 @@ function _elgg_cache_init() {  	}  	if ($CONFIG->system_cache_enabled && !$CONFIG->i18n_loaded_from_cache) { +		reload_all_translations();  		foreach ($CONFIG->translations as $lang => $map) {  			elgg_save_system_cache("$lang.php", serialize($map));  		} diff --git a/engine/lib/configuration.php b/engine/lib/configuration.php index 9bf1529d6..305aa00b6 100644 --- a/engine/lib/configuration.php +++ b/engine/lib/configuration.php @@ -476,10 +476,12 @@ function get_config($name, $site_guid = 0) {  			break;  	} +	// @todo these haven't really been implemented in Elgg 1.8. Complete in 1.9.  	// show dep message  	if ($new_name) { +	//	$msg = "Config value $name has been renamed as $new_name";  		$name = $new_name; -		elgg_deprecated_notice($msg, $dep_version); +	//	elgg_deprecated_notice($msg, $dep_version);  	}  	// decide from where to return the value diff --git a/engine/lib/database.php b/engine/lib/database.php index cc2b99f6a..7d90b30b8 100644 --- a/engine/lib/database.php +++ b/engine/lib/database.php @@ -253,6 +253,10 @@ function execute_query($query, $dblink) {  		throw new DatabaseException(elgg_echo('DatabaseException:InvalidQuery'));  	} +	if (!is_resource($dblink)) { +		throw new DatabaseException(elgg_echo('DatabaseException:InvalidDBLink')); +	} +  	$dbcalls++;  	$result = mysql_query($query, $dblink); diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index b55958a6c..26c1cccfd 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -1053,6 +1053,7 @@ function _elgg_php_exception_handler($exception) {   *   * @return true   * @access private + * @todo Replace error_log calls with elgg_log calls.   */  function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) {  	$error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)"; @@ -1068,7 +1069,12 @@ function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) {  		case E_WARNING :  		case E_USER_WARNING : -			error_log("PHP WARNING: $error"); +		case E_RECOVERABLE_ERROR: // (e.g. type hint violation) +			 +			// check if the error wasn't suppressed by @-functionname +			if(error_reporting()){ +				error_log("PHP WARNING: $error"); +			}  			break;  		default: @@ -1092,8 +1098,8 @@ function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) {   *   * @note No messages will be displayed unless debugging has been enabled.   * - * @param str $message User message - * @param str $level   NOTICE | WARNING | ERROR | DEBUG + * @param string $message User message + * @param string $level   NOTICE | WARNING | ERROR | DEBUG   *   * @return bool   * @since 1.7.0 @@ -1263,7 +1269,7 @@ function elgg_deprecated_notice($msg, $dep_version, $backtrace_level = 1) {  	$msg .= implode("<br /> -> ", $stack); -	elgg_dump($msg, elgg_is_admin_logged_in(), 'WARNING'); +	elgg_log($msg, 'WARNING');  	return true;  } @@ -1573,10 +1579,14 @@ function elgg_http_url_is_identical($url1, $url2, $ignore_params = array('offset   * @param bool   $strict  Return array key if it's set, even if empty. If false,   *                        return $default if the array key is unset or empty.   * - * @return void + * @return mixed   * @since 1.8.0   */ -function elgg_extract($key, array $array, $default = NULL, $strict = true) { +function elgg_extract($key, array $array, $default = null, $strict = true) { +	if (!is_array($array)) { +		return $default; +	} +  	if ($strict) {  		return (isset($array[$key])) ? $array[$key] : $default;  	} else { @@ -2014,10 +2024,20 @@ function elgg_is_valid_options_for_batch_operation($options, $type) {   *   * @link http://docs.elgg.org/Tutorials/WalledGarden   * @elgg_plugin_hook index system + * + * @param string $hook   The name of the hook + * @param string $type   The type of hook + * @param bool   $value  Has a plugin already rendered an index page? + * @param array  $params Array of parameters (should be empty)   * @return bool   * @access private   */ -function elgg_walled_garden_index() { +function elgg_walled_garden_index($hook, $type, $value, $params) { +	if ($value) { +		// do not create a second index page so return +		return; +	} +  	elgg_load_css('elgg.walled_garden');  	elgg_load_js('elgg.walled_garden'); @@ -2082,6 +2102,22 @@ function elgg_walled_garden() {  }  /** + * Remove public access for walled gardens + * + * @param string $hook + * @param string $type + * @param array $accesses + * @return array + * @access private + */ +function _elgg_walled_garden_remove_public_access($hook, $type, $accesses) { +	if (isset($accesses[ACCESS_PUBLIC])) { +		unset($accesses[ACCESS_PUBLIC]); +	} +	return $accesses; +} + +/**   * Boots the engine   *   * 1. sets error handlers @@ -2104,11 +2140,13 @@ function _elgg_engine_boot() {  	_elgg_load_application_config(); -	register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); -  	_elgg_load_site_config(); +	_elgg_session_boot(); +  	_elgg_load_cache(); + +	_elgg_load_translations();  }  /** @@ -2137,6 +2175,7 @@ function elgg_init() {  	elgg_register_js('jquery.easing', 'vendors/jquery/jquery.easing.1.3.packed.js');  	elgg_register_js('elgg.avatar_cropper', 'js/lib/ui.avatar_cropper.js');  	elgg_register_js('jquery.imgareaselect', 'vendors/jquery/jquery.imgareaselect-0.9.8/scripts/jquery.imgareaselect.min.js'); +	elgg_register_js('elgg.ui.river', 'js/lib/ui.river.js');  	elgg_register_css('jquery.imgareaselect', 'vendors/jquery/jquery.imgareaselect-0.9.8/css/imgareaselect-deprecated.css'); diff --git a/engine/lib/entities.php b/engine/lib/entities.php index 4875b2c2f..a50567d9f 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -30,7 +30,7 @@ $SUBTYPE_CACHE = NULL;   *   * @param int $guid The entity guid   * - * @return void + * @return null   * @access private   */  function invalidate_cache_for_entity($guid) { @@ -39,6 +39,8 @@ function invalidate_cache_for_entity($guid) {  	$guid = (int)$guid;  	unset($ENTITY_CACHE[$guid]); + +	elgg_get_metadata_cache()->clear($guid);  }  /** @@ -48,14 +50,27 @@ function invalidate_cache_for_entity($guid) {   *   * @param ElggEntity $entity Entity to cache   * - * @return void + * @return null   * @see retrieve_cached_entity()   * @see invalidate_cache_for_entity()   * @access private + * TODO(evan): Use an ElggCache object   */  function cache_entity(ElggEntity $entity) {  	global $ENTITY_CACHE; +	// Don't cache 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()) { +		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)]); +	} +  	$ENTITY_CACHE[$entity->guid] = $entity;  } @@ -64,7 +79,7 @@ function cache_entity(ElggEntity $entity) {   *   * @param int $guid The guid   * - * @return void + * @return ElggEntity|bool false if entity not cached, or not fully loaded   * @see cache_entity()   * @see invalidate_cache_for_entity()   * @access private @@ -313,6 +328,10 @@ function add_subtype($type, $subtype, $class = "") {  /**   * Removes a registered ElggEntity type, subtype, and classname.   * + * @warning You do not want to use this function. If you want to unregister + * a class for a subtype, use update_subtype(). Using this function will + * permanently orphan all the objects created with the specified subtype. + *   * @param string $type    Type   * @param string $subtype Subtype   * @@ -331,7 +350,7 @@ function remove_subtype($type, $subtype) {  }  /** - * Update a registered ElggEntity type, subtype, and classname + * Update a registered ElggEntity type, subtype, and class name   *   * @param string $type    Type   * @param string $subtype Subtype @@ -340,7 +359,7 @@ function remove_subtype($type, $subtype) {   * @return bool   */  function update_subtype($type, $subtype, $class = '') { -	global $CONFIG; +	global $CONFIG, $SUBTYPE_CACHE;  	if (!$id = get_subtype_id($type, $subtype)) {  		return FALSE; @@ -348,10 +367,16 @@ function update_subtype($type, $subtype, $class = '') {  	$type = sanitise_string($type);  	$subtype = sanitise_string($subtype); -	return update_data("UPDATE {$CONFIG->dbprefix}entity_subtypes +	$result = 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; +	} + +	return $result;  }  /** @@ -583,12 +608,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)) { @@ -666,29 +693,53 @@ function entity_row_to_elggstar($row) {   * @link http://docs.elgg.org/DataModel/Entities   */  function get_entity($guid) { -	static $newentity_cache; -	$new_entity = false; +	// This should not be a static local var. Notice that cache writing occurs in a completely +	// different instance outside this function. +	// @todo We need a single Memcache instance with a shared pool of namespace wrappers. This function would pull an instance from the pool. +	static $shared_cache;  	// We could also use: if (!(int) $guid) { return FALSE },   	// but that evaluates to a false positive for $guid = TRUE.  	// This is a bit slower, but more thorough.  	if (!is_numeric($guid) || $guid === 0 || $guid === '0') { -		return FALSE; +		return false; +	} +	 +	// Check local cache first +	$new_entity = retrieve_cached_entity($guid); +	if ($new_entity) { +		return $new_entity;  	} -	if ((!$newentity_cache) && (is_memcache_available())) { -		$newentity_cache = new ElggMemcache('new_entity_cache'); +	// Check shared memory cache, if available +	if (null === $shared_cache) { +		if (is_memcache_available()) { +			$shared_cache = new ElggMemcache('new_entity_cache'); +		} else { +			$shared_cache = false; +		}  	} -	if ($newentity_cache) { -		$new_entity = $newentity_cache->load($guid); +	// until ACLs in memcache, DB query is required to determine access +	$entity_row = get_entity_as_row($guid); +	if (!$entity_row) { +		return false;  	} -	if ($new_entity) { -		return $new_entity; +	if ($shared_cache) { +		$cached_entity = $shared_cache->load($guid); +		// @todo store ACLs in memcache http://trac.elgg.org/ticket/3018#comment:3 +		if ($cached_entity) { +			// @todo use ACL and cached entity access_id to determine if user can see it +			return $cached_entity; +		}  	} -	return entity_row_to_elggstar(get_entity_as_row($guid)); +	$new_entity = entity_row_to_elggstar($entity_row); +	if ($new_entity) { +		cache_entity($new_entity); +	} +	return $new_entity;  }  /** @@ -915,11 +966,11 @@ function elgg_get_entities(array $options = array()) {  	}  	if (!$options['count']) { -		if ($options['group_by'] = sanitise_string($options['group_by'])) { +		if ($options['group_by']) {  			$query .= " GROUP BY {$options['group_by']}";  		} -		if ($options['order_by'] = sanitise_string($options['order_by'])) { +		if ($options['order_by']) {  			$query .= " ORDER BY {$options['order_by']}";  		} @@ -930,6 +981,26 @@ function elgg_get_entities(array $options = array()) {  		}  		$dt = get_data($query, $options['callback']); +		if ($dt) { +			// populate entity and metadata caches +			$guids = array(); +			foreach ($dt as $item) { +				// A custom callback could result in items that aren't ElggEntity's, so check for them +				if ($item instanceof ElggEntity) { +					cache_entity($item); +					// plugins usually have only settings +					if (!$item instanceof ElggPlugin) { +						$guids[] = $item->guid; +					} +				} +			} +			// @todo Without this, recursive delete fails. See #4568 +			reset($dt); + +			if ($guids) { +				elgg_get_metadata_cache()->populateFromEntities($guids); +			} +		}  		return $dt;  	} else {  		$total = get_data_row($query); @@ -1103,7 +1174,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   */ @@ -1152,7 +1223,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   */ @@ -1254,7 +1325,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   */ @@ -1402,6 +1473,7 @@ function disable_entity($guid, $reason = "", $recursive = true) {  				$entity->disableMetadata();  				$entity->disableAnnotations(); +				invalidate_cache_for_entity($guid);  				$res = update_data("UPDATE {$CONFIG->dbprefix}entities  					SET enabled = 'no' @@ -1452,6 +1524,7 @@ function enable_entity($guid, $recursive = true) {  						'relationship' => 'disabled_with',  						'relationship_guid' => $entity->guid,  						'inverse_relationship' => true, +						'limit' => 0,  					));  					foreach ($disabled_with_it as $e) { @@ -1597,7 +1670,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 @@ -1642,6 +1715,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 @@ -1684,6 +1759,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'); @@ -1755,6 +1832,8 @@ 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']; @@ -1767,7 +1846,7 @@ function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) {  		if ($tmp) {  			// Make sure its saved  			if (!$tmp->save()) { -				elgg_echo('ImportException:ProblemSaving', array($element->getAttribute('uuid'))); +				$msg = elgg_echo('ImportException:ProblemSaving', array($element->getAttribute('uuid')));  				throw new ImportException($msg);  			} @@ -1801,8 +1880,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) { @@ -1926,7 +2003,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 @@ -1962,7 +2039,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 @@ -1999,7 +2076,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) { @@ -2066,7 +2143,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; @@ -2266,7 +2343,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 ffd3c1357..43421342c 100644 --- a/engine/lib/extender.php +++ b/engine/lib/extender.php @@ -105,6 +105,7 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params)  		// Save  		if (!$entity->save()) { +			$attr_name = $element->getAttribute('name');  			$msg = elgg_echo('ImportException:ProblemUpdatingMeta', array($attr_name, $entity_uuid));  			throw new ImportException($msg);  		} @@ -120,7 +121,7 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params)   * @param string $type        'metadata' or 'annotation'   * @param int    $user_guid   The GUID of the user   * - * @return true|false + * @return bool   */  function can_edit_extender($extender_id, $type, $user_guid = 0) {  	if (!elgg_is_logged_in()) { @@ -155,7 +156,7 @@ function can_edit_extender($extender_id, $type, $user_guid = 0) {  	}  	// Trigger plugin hooks -	$params = array('entity' => $entity, 'user' => $user); +	$params = array('entity' => $extender->getEntity(), 'user' => $user);  	return elgg_trigger_plugin_hook('permissions_check', $type, $params, false);  } diff --git a/engine/lib/filestore.php b/engine/lib/filestore.php index 86f6d9baa..93a127257 100644 --- a/engine/lib/filestore.php +++ b/engine/lib/filestore.php @@ -149,6 +149,12 @@ $x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $upscale = FALSE) {  		return FALSE;  	} +	// color transparencies white (default is black) +	imagefilledrectangle( +		$new_image, 0, 0, $params['newwidth'], $params['newheight'], +		imagecolorallocate($new_image, 255, 255, 255) +	); +  	$rtn_code = imagecopyresampled(	$new_image,  									$original_image,  									0, diff --git a/engine/lib/input.php b/engine/lib/input.php index dda8211b6..6d1646e1a 100644 --- a/engine/lib/input.php +++ b/engine/lib/input.php @@ -283,7 +283,7 @@ function input_livesearch_page_handler($page) {  					WHERE e.guid = ue.guid  						AND e.enabled = 'yes'  						AND ue.banned = 'no' -						AND (ue.name LIKE '$q%' OR ue.username LIKE '$q%') +						AND (ue.name LIKE '$q%' OR ue.name LIKE '% $q%' OR ue.username LIKE '$q%')  					LIMIT $limit  				"; @@ -333,7 +333,7 @@ function input_livesearch_page_handler($page) {  					WHERE e.guid = ge.guid  						AND e.enabled = 'yes'  						$owner_where -						AND (ge.name LIKE '$q%' OR ge.description LIKE '%$q%') +						AND (ge.name LIKE '$q%' OR ge.name LIKE '% $q%' OR ge.description LIKE '% $q%')  					LIMIT $limit  				";  				if ($entities = get_data($query)) { @@ -379,7 +379,7 @@ function input_livesearch_page_handler($page) {  						AND e.guid = ue.guid  						AND e.enabled = 'yes'  						AND ue.banned = 'no' -						AND (ue.name LIKE '$q%' OR ue.username LIKE '$q%') +						AND (ue.name LIKE '$q%' OR ue.name LIKE '% $q%' OR ue.username LIKE '$q%')  					LIMIT $limit  				"; diff --git a/engine/lib/languages.php b/engine/lib/languages.php index bf6829a39..98006f7cd 100644 --- a/engine/lib/languages.php +++ b/engine/lib/languages.php @@ -8,6 +8,65 @@   */  /** + * Given a message key, returns an appropriately translated full-text string + * + * @param string $message_key The short message code + * @param array  $args        An array of arguments to pass through vsprintf(). + * @param string $language    Optionally, the standard language code + *                            (defaults to site/user default, then English) + * + * @return string Either the translated string, the English string, + * or the original language string. + */ +function elgg_echo($message_key, $args = array(), $language = "") { +	global $CONFIG; + +	static $CURRENT_LANGUAGE; + +	// old param order is deprecated +	if (!is_array($args)) { +		elgg_deprecated_notice( +			'As of Elgg 1.8, the 2nd arg to elgg_echo() is an array of string replacements and the 3rd arg is the language.', +			1.8 +		); + +		$language = $args; +		$args = array(); +	} + +	if (!isset($CONFIG->translations)) { +		// this means we probably had an exception before translations were initialized +		register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); +	} + +	if (!$CURRENT_LANGUAGE) { +		$CURRENT_LANGUAGE = get_language(); +	} +	if (!$language) { +		$language = $CURRENT_LANGUAGE; +	} + +	if (isset($CONFIG->translations[$language][$message_key])) { +		$string = $CONFIG->translations[$language][$message_key]; +	} else if (isset($CONFIG->translations["en"][$message_key])) { +		$string = $CONFIG->translations["en"][$message_key]; +		$lang = $CONFIG->translations["en"][$language]; +		elgg_log(sprintf('Missing %s translation for "%s" language key', $lang, $message_key), 'NOTICE'); +	} else { +		$string = $message_key; +		elgg_log(sprintf('Missing English translation for "%s" language key', $message_key), 'NOTICE'); +	} + +	// only pass through if we have arguments to allow backward compatibility +	// with manual sprintf() calls. +	if ($args) { +		$string = vsprintf($string, $args); +	} + +	return $string; +} + +/**   * Add a translation.   *   * Translations are arrays in the Zend Translation array format, eg: @@ -82,56 +141,34 @@ function get_language() {  	return false;  } -/** - * Given a message shortcode, returns an appropriately translated full-text string - * - * @param string $message_key The short message code - * @param array  $args        An array of arguments to pass through vsprintf(). - * @param string $language    Optionally, the standard language code - *                            (defaults to site/user default, then English) - * - * @return string Either the translated string, the English string, - * or the original language string. - */ -function elgg_echo($message_key, $args = array(), $language = "") { +function _elgg_load_translations() {  	global $CONFIG; -	static $CURRENT_LANGUAGE; - -	// old param order is deprecated -	if (!is_array($args)) { -		elgg_deprecated_notice( -			'As of Elgg 1.8, the 2nd arg to elgg_echo() is an array of string replacements and the 3rd arg is the language.', -			1.8 -		); - -		$language = $args; -		$args = array(); -	} +	if ($CONFIG->system_cache_enabled) { +		$loaded = true; +		$languages = array_unique(array('en', get_current_language())); +		foreach ($languages as $language) { +			$data = elgg_load_system_cache("$language.php"); +			if ($data) { +				add_translation($language, unserialize($data)); +			} else { +				$loaded = false; +			} +		} -	if (!$CURRENT_LANGUAGE) { -		$CURRENT_LANGUAGE = get_language(); -	} -	if (!$language) { -		$language = $CURRENT_LANGUAGE; +		if ($loaded) { +			$CONFIG->i18n_loaded_from_cache = true; +			// this is here to force  +			$CONFIG->language_paths[dirname(dirname(dirname(__FILE__))) . "/languages/"] = true; +			return; +		}  	} -	if (isset($CONFIG->translations[$language][$message_key])) { -		$string = $CONFIG->translations[$language][$message_key]; -	} else if (isset($CONFIG->translations["en"][$message_key])) { -		$string = $CONFIG->translations["en"][$message_key]; -	} else { -		$string = $message_key; -	} +	// load core translations from languages directory +	register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); +} -	// only pass through if we have arguments to allow backward compatibility -	// with manual sprintf() calls. -	if ($args) { -		$string = vsprintf($string, $args); -	} -	return $string; -}  /**   * When given a full path, finds translation files and loads them @@ -145,16 +182,9 @@ function elgg_echo($message_key, $args = array(), $language = "") {  function register_translations($path, $load_all = false) {  	global $CONFIG; -	static $load_from_cache; -	static $cache_loaded_langs; -	if (!isset($load_from_cache)) { -		$load_from_cache = $CONFIG->system_cache_enabled; -		$cache_loaded_langs = array(); -	} -  	$path = sanitise_filepath($path); -	// Make a note of this path just in case we need to register this language later +	// Make a note of this path just incase we need to register this language later  	if (!isset($CONFIG->language_paths)) {  		$CONFIG->language_paths = array();  	} @@ -162,6 +192,7 @@ function register_translations($path, $load_all = false) {  	// Get the current language based on site defaults and user preference  	$current_language = get_current_language(); +	elgg_log("Translations loaded from: $path");  	// only load these files unless $load_all is true.  	$load_language_files = array( @@ -171,32 +202,6 @@ function register_translations($path, $load_all = false) {  	$load_language_files = array_unique($load_language_files); -	if ($load_from_cache && !$load_all) { -		// load language files from cache -		$data = array(); -		foreach ($load_language_files as $lang_file) { -			$lang = substr($lang_file, 0, strpos($lang_file, '.')); -			if (!isset($cache_loaded_langs[$lang])) { -				$data[$lang] = elgg_load_system_cache($lang_file); -				if ($data[$lang]) { -					$cache_loaded_langs[$lang] = true; -				} else { -					// this language file not cached yet -					$load_from_cache = false; -				} -			} -		} - -		// are we still suppose to load from cache -		if ($load_from_cache) { -			foreach ($data as $lang => $map) { -				add_translation($lang, unserialize($map)); -			} -			$CONFIG->i18n_loaded_from_cache = true; -			return true; -		} -	} -  	$handle = opendir($path);  	if (!$handle) {  		elgg_log("Could not open language path: $path", 'ERROR'); @@ -218,11 +223,6 @@ function register_translations($path, $load_all = false) {  		}  	} -	elgg_log("Translations loaded from: $path"); - -	// make sure caching code saves language data if system cache is on -	$CONFIG->i18n_loaded_from_cache = false; -  	return $return;  } @@ -344,7 +344,7 @@ function get_missing_language_keys($language) {   */  function elgg_languages_init() {  	$lang = get_current_language(); -	elgg_register_simplecache_view("cache/js/languages/$lang"); +	elgg_register_simplecache_view("js/languages/$lang");  }  elgg_register_event_handler('init', 'system', 'elgg_languages_init'); diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php index 0ff3a43dc..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');  } @@ -361,13 +362,24 @@ function elgg_enable_metadata(array $options) {   * options available to elgg_get_entities().  Supports   * the singular option shortcut.   * - * NB: Using metadata_names and metadata_values results in a + * @note Using metadata_names and metadata_values results in a   * "names IN (...) AND values IN (...)" clause.  This is subtly   * differently than default multiple metadata_name_value_pairs, which use   * "(name = value) AND (name = value)" clauses.   *   * When in doubt, use name_value_pairs.   * + * To ask for entities that do not have a metadata value, use a custom + * where clause like this: + * + * 	$options['wheres'][] = "NOT EXISTS ( + *			SELECT 1 FROM {$dbprefix}metadata md + *			WHERE md.entity_guid = e.guid + *				AND md.name_id = $name_metastring_id + *				AND md.value_id = $value_metastring_id)"; + * + * Note the metadata name and value has been denormalized in the above example. + *   * @see elgg_get_entities   *   * @param array $options Array in format: @@ -438,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 @@ -721,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 @@ -732,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();  		} @@ -878,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); @@ -901,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 a7984ce5a..8c3952594 100644 --- a/engine/lib/navigation.php +++ b/engine/lib/navigation.php @@ -230,7 +230,7 @@ function elgg_pop_breadcrumb() {  	global $CONFIG;  	if (is_array($CONFIG->breadcrumbs)) { -		array_pop($CONFIG->breadcrumbs); +		return array_pop($CONFIG->breadcrumbs);  	}  	return FALSE; @@ -335,6 +335,18 @@ function elgg_river_menu_setup($hook, $type, $return, $params) {  				$return[] = ElggMenuItem::factory($options);  			}  		} +		 +		if (elgg_is_admin_logged_in()) { +			$options = array( +				'name' => 'delete', +				'href' => elgg_add_action_tokens_to_url("action/river/delete?id=$item->id"), +				'text' => elgg_view_icon('delete'), +				'title' => elgg_echo('delete'), +				'confirm' => elgg_echo('deleteconfirm'), +				'priority' => 200, +			); +			$return[] = ElggMenuItem::factory($options); +		}  	}  	return $return; diff --git a/engine/lib/notification.php b/engine/lib/notification.php index 5a2f5f8ac..18faff27f 100644 --- a/engine/lib/notification.php +++ b/engine/lib/notification.php @@ -480,8 +480,8 @@ function object_notifications($event, $object_type, $object) {  		}  		if (isset($CONFIG->register_objects[$object_type][$object_subtype])) { -			$descr = $CONFIG->register_objects[$object_type][$object_subtype]; -			$string = $descr . ": " . $object->getURL(); +			$subject = $CONFIG->register_objects[$object_type][$object_subtype]; +			$string = $subject . ": " . $object->getURL();  			// Get users interested in content from this person and notify them  			// (Person defined by container_guid so we can also subscribe to groups if we want) @@ -500,16 +500,16 @@ function object_notifications($event, $object_type, $object) {  						if ($user instanceof ElggUser && !$user->isBanned()) {  							if (($user->guid != $SESSION['user']->guid) && has_access_to_entity($object, $user)  							&& $object->access_id != ACCESS_PRIVATE) { -								$methodstring = elgg_trigger_plugin_hook('notify:entity:message', $object->getType(), array( +								$body = elgg_trigger_plugin_hook('notify:entity:message', $object->getType(), array(  									'entity' => $object,  									'to_entity' => $user,  									'method' => $method), $string); -								if (empty($methodstring) && $methodstring !== false) { -									$methodstring = $string; +								if (empty($body) && $body !== false) { +									$body = $string;  								} -								if ($methodstring !== false) { -									notify_user($user->guid, $object->container_guid, $descr, $methodstring, -										NULL, array($method)); +								if ($body !== false) { +									notify_user($user->guid, $object->container_guid, $subject, $body, +										null, array($method));  								}  							}  						} diff --git a/engine/lib/output.php b/engine/lib/output.php index b1245a924..0069360f0 100644 --- a/engine/lib/output.php +++ b/engine/lib/output.php @@ -271,8 +271,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; @@ -310,19 +310,11 @@ function elgg_get_friendly_title($title) {  		return $result;  	} -	// @todo not using this because of locale concerns -	//$title = iconv('UTF-8', 'ASCII//TRANSLIT', $title); +	// handle some special cases +	$title = str_replace('&', 'and', $title); -	// @todo this uses a utf8 character class. can use if -	// we want to support utf8 in the url. -	//$title = preg_replace('/[^\p{L}\- ]/u', '', $title); +	$title = ElggTranslit::urlize($title); -	// use A-Za-z0-9_ instead of \w because \w is locale sensitive -	$title = preg_replace("/[^A-Za-z0-9_\- ]/", "", $title); -	$title = str_replace(" ", "-", $title); -	$title = str_replace("--", "-", $title); -	$title = trim($title); -	$title = elgg_strtolower($title);  	return $title;  } diff --git a/engine/lib/pagehandler.php b/engine/lib/pagehandler.php index a675d976a..ba7518a77 100644 --- a/engine/lib/pagehandler.php +++ b/engine/lib/pagehandler.php @@ -31,18 +31,18 @@ function page_handler($handler, $page) {  	}  	// return false to stop processing the request (because you handled it) -	// return a new $params array if you want to route the request differently -	$params = array( +	// return a new $request array if you want to route the request differently +	$request = array(  		'handler' => $handler,  		'segments' => $page,  	); -	$params = elgg_trigger_plugin_hook('route', $handler, NULL, $params); -	if ($params === false) { +	$request = elgg_trigger_plugin_hook('route', $handler, null, $request); +	if ($request === false) {  		return true;  	} -	$handler = $params['handler']; -	$page = $params['segments']; +	$handler = $request['handler']; +	$page = $request['segments'];  	$result = false;  	if (isset($CONFIG->pagehandler) && !empty($handler) && isset($CONFIG->pagehandler[$handler])) { @@ -129,7 +129,7 @@ function elgg_error_page_handler($hook, $type, $result, $params) {  		$content = elgg_view("errors/default", $params);  	}  	$body = elgg_view_layout('error', array('content' => $content)); -	echo elgg_view_page($title, $body, 'error'); +	echo elgg_view_page('', $body, 'error');  	exit;  } diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php index 39a76db5d..d5cd4fe76 100644 --- a/engine/lib/plugins.php +++ b/engine/lib/plugins.php @@ -311,6 +311,10 @@ function elgg_load_plugins() {  		$start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_VIEWS;  	} +	if (elgg_get_config('i18n_loaded_from_cache')) { +		$start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_LANGUAGES; +	} +  	$return = true;  	$plugins = elgg_get_plugins('active');  	if ($plugins) { diff --git a/engine/lib/relationships.php b/engine/lib/relationships.php index f50c4a485..09d541e22 100644 --- a/engine/lib/relationships.php +++ b/engine/lib/relationships.php @@ -239,6 +239,15 @@ function get_entity_relationships($guid, $inverse_relationship = FALSE) {   * Also accepts all options available to elgg_get_entities() and   * elgg_get_entities_from_metadata().   * + * To ask for entities that do not have a particulat relationship to an entity, + * use a custom where clause like the following: + * + * 	$options['wheres'][] = "NOT EXISTS ( + *			SELECT 1 FROM {$db_prefix}entity_relationships + *				WHERE guid_one = e.guid + *				AND relationship = '$relationship' + *		)"; + *   * @see elgg_get_entities   * @see elgg_get_entities_from_metadata   * diff --git a/engine/lib/river.php b/engine/lib/river.php index 547d9495e..b717a7756 100644 --- a/engine/lib/river.php +++ b/engine/lib/river.php @@ -55,7 +55,7 @@ $posted = 0, $annotation_id = 0) {  	$posted = sanitise_int($posted);  	$annotation_id = sanitise_int($annotation_id); -	$params = array( +	$values = array(  		'type' => $type,  		'subtype' => $subtype,  		'action_type' => $action_type, @@ -68,13 +68,13 @@ $posted = 0, $annotation_id = 0) {  	);  	// return false to stop insert -	$params = elgg_trigger_plugin_hook('creating', 'river', null, $params); -	if ($params == false) { +	$values = elgg_trigger_plugin_hook('creating', 'river', null, $values); +	if ($values == false) {  		// inserting did not fail - it was just prevented  		return true;  	} -	extract($params); +	extract($values);  	// Attempt to save river item; return success status  	$id = insert_data("insert into {$CONFIG->dbprefix}river " . @@ -643,9 +643,11 @@ function elgg_river_init() {  	elgg_register_page_handler('activity', 'elgg_river_page_handler');  	$item = new ElggMenuItem('activity', elgg_echo('activity'), 'activity');  	elgg_register_menu_item('site', $item); - +	  	elgg_register_widget_type('river_widget', elgg_echo('river:widget:title'), elgg_echo('river:widget:description')); +	elgg_register_action('river/delete', '', 'admin'); +  	elgg_register_plugin_hook_handler('unit_test', 'system', 'elgg_river_test');  } diff --git a/engine/lib/sessions.php b/engine/lib/sessions.php index 419d36707..72ca0a1c2 100644 --- a/engine/lib/sessions.php +++ b/engine/lib/sessions.php @@ -376,14 +376,10 @@ function logout() {   *   * @uses $_SESSION   * - * @param string $event       Event name - * @param string $object_type Object type - * @param mixed  $object      Object - *   * @return bool   * @access private   */ -function _elgg_session_boot($event, $object_type, $object) { +function _elgg_session_boot() {  	global $DB_PREFIX, $CONFIG;  	// Use database for sessions @@ -464,9 +460,6 @@ function _elgg_session_boot($event, $object_type, $object) {  		return false;  	} -	// Since we have loaded a new user, this user may have different language preferences -	register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); -  	return true;  } @@ -658,5 +651,3 @@ function _elgg_session_gc($maxlifetime) {  	return true;  } - -elgg_register_event_handler('boot', 'system', '_elgg_session_boot', 2); diff --git a/engine/lib/sites.php b/engine/lib/sites.php index 850092cad..8b772668d 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;  }  /** diff --git a/engine/lib/statistics.php b/engine/lib/statistics.php index e1f95ed97..5ee640549 100644 --- a/engine/lib/statistics.php +++ b/engine/lib/statistics.php @@ -95,8 +95,8 @@ function get_number_users($show_deactivated = false) {   * @return string    */  function get_online_users() { -	$count = find_active_users(600, 10, $offset, true); -	$objects = find_active_users(600, 10, $offset); +	$count = find_active_users(600, 10, 0, true); +	$objects = find_active_users(600, 10);  	if ($objects) {  		return elgg_view_entity_list($objects, array( diff --git a/engine/lib/system_log.php b/engine/lib/system_log.php index 28d90be56..53fa24557 100644 --- a/engine/lib/system_log.php +++ b/engine/lib/system_log.php @@ -156,9 +156,8 @@ function get_object_from_log_entry($entry_id) {   * This is called by the event system and should not be called directly.   *   * @param object $object The object you're talking about. - * @param string $event  String The event being logged - * - * @return mixed + * @param string $event  The event being logged + * @return void   */  function system_log($object, $event) {  	global $CONFIG; @@ -166,6 +165,12 @@ function system_log($object, $event) {  	static $cache_size = 0;  	if ($object instanceof Loggable) { + +		if (datalist_get('version') < 2012012000) { +			// this is a site that doesn't have the ip_address column yet +			return; +		} +  		// reset cache if it has grown too large  		if (!is_array($log_cache) || $cache_size > 500) {  			$log_cache = array(); @@ -213,8 +218,6 @@ function system_log($object, $event) {  			$log_cache[$time][$object_id][$event] = true;  			$cache_size += 1;  		} - -		return true;  	}  } diff --git a/engine/lib/upgrades/2010121602.php b/engine/lib/upgrades/2010121602.php index 2d55c8214..5b0996b5e 100644 --- a/engine/lib/upgrades/2010121602.php +++ b/engine/lib/upgrades/2010121602.php @@ -4,7 +4,7 @@   */  $query = "UPDATE {$CONFIG->dbprefix}river -			SET view='river/annotation/generic_comment/create', action_type='create' +			SET view='river/annotation/generic_comment/create'  			WHERE view='annotation/annotate' AND action_type='comment'";  update_data($query); diff --git a/engine/lib/users.php b/engine/lib/users.php index e209f2c38..527eff3cd 100644 --- a/engine/lib/users.php +++ b/engine/lib/users.php @@ -136,7 +136,6 @@ function ban_user($user_guid, $reason = "") {  	global $CONFIG;  	$user_guid = (int)$user_guid; -	$reason = sanitise_string($reason);  	$user = get_entity($user_guid); @@ -810,6 +809,12 @@ function validate_username($username) {  		$msg = elgg_echo('registration:usernametooshort', array($CONFIG->minusername));  		throw new RegistrationException($msg);  	} +	 +	// username in the database has a limit of 128 characters +	if (strlen($username) > 128) { +		$msg = elgg_echo('registration:usernametoolong', array(128)); +		throw new RegistrationException($msg); +	}  	// Blacklist for bad characters (partially nicked from mediawiki)  	$blacklist = '/[' . @@ -1556,7 +1561,7 @@ function users_init() {  	elgg_register_action('friends/remove');  	elgg_register_action('avatar/upload');  	elgg_register_action('avatar/crop'); -	elgg_register_action('avatar/revert'); +	elgg_register_action('avatar/remove');  	elgg_register_action('profile/edit');  	elgg_register_action('friends/collections/add'); diff --git a/engine/lib/views.php b/engine/lib/views.php index 1b013be6f..6135026a7 100644 --- a/engine/lib/views.php +++ b/engine/lib/views.php @@ -303,7 +303,7 @@ function elgg_set_view_location($view, $location, $viewtype = '') {  /**   * Returns whether the specified view exists   * - * @note If $recurse is strue, also checks if a view exists only as an extension. + * @note If $recurse is true, also checks if a view exists only as an extension.   *   * @param string $view     The view name   * @param string $viewtype If set, forces the viewtype @@ -403,7 +403,7 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie  	$view_orig = $view;  	// Trigger the pagesetup event -	if (!isset($CONFIG->pagesetupdone)) { +	if (!isset($CONFIG->pagesetupdone) && $CONFIG->boot_complete) {  		$CONFIG->pagesetupdone = true;  		elgg_trigger_event('pagesetup', 'system');  	} @@ -1224,12 +1224,12 @@ function elgg_view_image_block($image, $body, $vars = array()) {   * @param string $type  The type of module (main, info, popup, aside, etc.)   * @param string $title A title to put in the header   * @param string $body  Content of the module - * @param string $vars  Additional parameters for the module + * @param array  $vars  Additional parameters for the module   *   * @return string   * @since 1.8.0   */ -function elgg_view_module($type, $title, $body, $vars = array()) { +function elgg_view_module($type, $title, $body, array $vars = array()) {  	$vars['class'] = elgg_extract('class', $vars, '') . " elgg-module-$type";  	$vars['title'] = $title; @@ -1653,7 +1653,7 @@ function elgg_views_boot() {  	}  	// 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/start.php b/engine/start.php index 506e27380..55b8ffa5b 100644 --- a/engine/start.php +++ b/engine/start.php @@ -49,6 +49,7 @@ global $CONFIG;  if (!isset($CONFIG)) {  	$CONFIG = new stdClass;  } +$CONFIG->boot_complete = false;  $lib_dir = dirname(__FILE__) . '/lib/'; @@ -99,11 +100,22 @@ elgg_trigger_event('boot', 'system');  // Load the plugins that are active  elgg_load_plugins(); + +// @todo move loading plugins into a single boot function that replaces 'boot', 'system' event +// and then move this code in there. +// This validates the view type - first opportunity to do it is after plugins load. +$view_type = elgg_get_viewtype(); +if (!elgg_is_valid_view_type($view_type)) { +	elgg_set_viewtype('default'); +} +  // @todo deprecate as plugins can use 'init', 'system' event  elgg_trigger_event('plugins_boot', 'system');  // Complete the boot process for both engine and plugins  elgg_trigger_event('init', 'system'); +$CONFIG->boot_complete = true; +  // System loaded and ready  elgg_trigger_event('ready', 'system'); diff --git a/engine/tests/api/metadata.php b/engine/tests/api/metadata.php index 2461e975e..9933263d1 100644 --- a/engine/tests/api/metadata.php +++ b/engine/tests/api/metadata.php @@ -28,6 +28,9 @@ class ElggCoreMetadataAPITest extends ElggCoreUnitTest {  	public function testGetMetastringById() {  		foreach (array('metaUnitTest', 'metaunittest', 'METAUNITTEST') as $string) { +			// since there is no guarantee that metastrings are garbage collected +			// between unit test runs, we delete before testing +			$this->delete_metastrings($string);  			$this->create_metastring($string);  		} @@ -43,9 +46,6 @@ class ElggCoreMetadataAPITest extends ElggCoreUnitTest {  		{  			$this->assertTrue(in_array($string, $this->metastrings));  		} - -		// clean up -		$this->delete_metastrings();  	}  	public function testElggGetEntitiesFromMetadata() { @@ -77,7 +77,6 @@ class ElggCoreMetadataAPITest extends ElggCoreUnitTest {  		// clean up  		$this->object->delete(); -		$this->delete_metastrings();  	}  	public function testElggGetMetadataCount() { @@ -198,20 +197,20 @@ class ElggCoreMetadataAPITest extends ElggCoreUnitTest {  		$u2->delete();  	} - -	protected function create_metastring($string) { +	protected function delete_metastrings($string) {  		global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE;  		$METASTRINGS_CACHE = $METASTRINGS_DEADNAME_CACHE = array(); -		mysql_query("INSERT INTO {$CONFIG->dbprefix}metastrings (string) VALUES ('$string')"); -		$this->metastrings[$string] = mysql_insert_id(); +		$string = sanitise_string($string); +		mysql_query("DELETE FROM {$CONFIG->dbprefix}metastrings WHERE string = BINARY '$string'");  	} -	protected function delete_metastrings() { +	protected function create_metastring($string) {  		global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE;  		$METASTRINGS_CACHE = $METASTRINGS_DEADNAME_CACHE = array(); -		$strings = implode(', ', $this->metastrings); -		mysql_query("DELETE FROM {$CONFIG->dbprefix}metastrings WHERE id IN ($strings)"); +		$string = sanitise_string($string); +		mysql_query("INSERT INTO {$CONFIG->dbprefix}metastrings (string) VALUES ('$string')"); +		$this->metastrings[$string] = mysql_insert_id();  	}  } diff --git a/engine/tests/api/metadata_cache.php b/engine/tests/api/metadata_cache.php new file mode 100644 index 000000000..846116a7b --- /dev/null +++ b/engine/tests/api/metadata_cache.php @@ -0,0 +1,169 @@ +<?php +/** + * Elgg Test metadata cache + * + * @package Elgg + * @subpackage Test + */ +class ElggCoreMetadataCacheTest extends ElggCoreUnitTest { + +	/** +	 * @var ElggVolatileMetadataCache +	 */ +	protected $cache; + +	/** +	 * @var ElggObject +	 */ +	protected $obj1; + +	/** +	 * @var int +	 */ +	protected $guid1; + +	/** +	 * @var ElggObject +	 */ +	protected $obj2; + +	/** +	 * @var int +	 */ +	protected $guid2; + +	protected $name = 'test'; +	protected $value = 'test'; +	protected $ignoreAccess; + +	/** +	 * Called before each test method. +	 */ +	public function setUp() { +		$this->ignoreAccess = elgg_set_ignore_access(false); + +		$this->cache = elgg_get_metadata_cache(); + +		$this->obj1 = new ElggObject(); +		$this->obj1->save(); +		$this->guid1 = $this->obj1->guid; + +		$this->obj2 = new ElggObject(); +		$this->obj2->save(); +		$this->guid2 = $this->obj2->guid; +	} + +	/** +	 * Called after each test method. +	 */ +	public function tearDown() { +		$this->obj1->delete(); +		$this->obj2->delete(); + +		elgg_set_ignore_access($this->ignoreAccess); +	} + +	public function testBasicApi() { +		// test de-coupled instance +		$cache = new ElggVolatileMetadataCache(); +		$cache->setIgnoreAccess(false); +		$guid = 1; + +		$this->assertFalse($cache->isKnown($guid, $this->name)); + +		$cache->markEmpty($guid, $this->name); +		$this->assertTrue($cache->isKnown($guid, $this->name)); +		$this->assertNull($cache->load($guid, $this->name)); + +		$cache->markUnknown($guid, $this->name); +		$this->assertFalse($cache->isKnown($guid, $this->name)); + +		$cache->save($guid, $this->name, $this->value); +		$this->assertIdentical($cache->load($guid, $this->name), $this->value); + +		$cache->save($guid, $this->name, 1, true); +		$this->assertIdentical($cache->load($guid, $this->name), array($this->value, 1)); + +		$cache->clear($guid); +		$this->assertFalse($cache->isKnown($guid, $this->name)); +	} + +	public function testReadsAreCached() { +		// test that reads fill cache +		$this->obj1->setMetaData($this->name, $this->value); +		$this->cache->flush(); + +		$this->obj1->getMetaData($this->name); +		$this->assertIdentical($this->cache->load($this->guid1, $this->name), $this->value); +	} + +	public function testWritesAreCached() { +		// delete should mark cache as known to be empty +		$this->obj1->deleteMetadata($this->name); +		$this->assertTrue($this->cache->isKnown($this->guid1, $this->name)); +		$this->assertNull($this->cache->load($this->guid1, $this->name)); + +		// without name, delete should invalidate the entire entity +		$this->cache->save($this->guid1, $this->name, $this->value); +		elgg_delete_metadata(array( +			'guid' => $this->guid1, +		)); +		$this->assertFalse($this->cache->isKnown($this->guid1, $this->name)); + +		// test set +		$this->obj1->setMetaData($this->name, $this->value); +		$this->assertIdentical($this->cache->load($this->guid1, $this->name), $this->value); + +		// test set multiple +		$this->obj1->setMetaData($this->name, 1, 'integer', true); +		$this->assertIdentical($this->cache->load($this->guid1, $this->name), array($this->value, 1)); + +		// writes when access is ignore should invalidate +		$tmp_ignore = elgg_set_ignore_access(true); +		$this->obj1->setMetaData($this->name, $this->value); +		$this->assertFalse($this->cache->isKnown($this->guid1, $this->name)); +		elgg_set_ignore_access($tmp_ignore); +	} + +	public function testDisableAndEnable() { +		// both should mark cache unknown +		$this->obj1->setMetaData($this->name, $this->value); +		$this->obj1->disableMetadata($this->name); +		$this->assertFalse($this->cache->isKnown($this->guid1, $this->name)); + +		$this->cache->save($this->guid1, $this->name, $this->value); +		$this->obj1->enableMetadata($this->name); +		$this->assertFalse($this->cache->isKnown($this->guid1, $this->name)); +	} + +	public function testPopulateFromEntities() { +		// test populating cache from set of entities +		$this->obj1->setMetaData($this->name, $this->value); +		$this->obj1->setMetaData($this->name, 4, 'integer', true); +		$this->obj1->setMetaData("{$this->name}-2", "{$this->value}-2"); +		$this->obj2->setMetaData($this->name, $this->value); + +		$this->cache->flush(); +		$this->cache->populateFromEntities(array($this->guid1, $this->guid2)); + +		$expected = array(); +		$expected[$this->name][] = $this->value; +		$expected[$this->name][] = 4; +		$expected["{$this->name}-2"] = "{$this->value}-2"; +		$this->assertIdentical($this->cache->loadAll($this->guid1), $expected); + +		$expected = array(); +		$expected[$this->name] = $this->value; +		$this->assertIdentical($this->cache->loadAll($this->guid2), $expected); +	} + +	public function testFilterHeavyEntities() { +		$big_str = str_repeat('-', 5000); +		$this->obj2->setMetaData($this->name, array($big_str, $big_str)); + +		$guids = array($this->guid1, $this->guid2); +		$expected = array($this->guid1); +		$actual = $this->cache->filterMetadataHeavyEntities($guids, 6000); +		$this->assertIdentical($actual, $expected); +	} +} diff --git a/engine/tests/api/plugins.php b/engine/tests/api/plugins.php index 8ecb0a46c..114f3991b 100644 --- a/engine/tests/api/plugins.php +++ b/engine/tests/api/plugins.php @@ -68,6 +68,9 @@ class ElggCorePluginsAPITest extends ElggCoreUnitTest {  			'blurb' => 'A concise description.',  			'description' => 'A longer, more interesting description.',  			'website' => 'http://www.elgg.org/', +			'repository' => 'https://github.com/Elgg/Elgg', +			'bugtracker' => 'http://trac.elgg.org', +			'donations' => 'http://elgg.org/supporter.php',  			'copyright' => '(C) Elgg Foundation 2011',  			'license' => 'GNU General Public License version 2', @@ -164,6 +167,21 @@ class ElggCorePluginsAPITest extends ElggCoreUnitTest {  		$this->assertEqual($this->manifest18->getWebsite(), 'http://www.elgg.org/');  		$this->assertEqual($this->manifest17->getWebsite(), 'http://www.elgg.org/');  	} +	 +	public function testElggPluginManifestGetRepository() { +		$this->assertEqual($this->manifest18->getRepositoryURL(), 'https://github.com/Elgg/Elgg'); +		$this->assertEqual($this->manifest17->getRepositoryURL(), ''); +	} +	 +		public function testElggPluginManifestGetBugtracker() { +		$this->assertEqual($this->manifest18->getBugTrackerURL(), 'http://trac.elgg.org'); +		$this->assertEqual($this->manifest17->getBugTrackerURL(), ''); +	} +	 +		public function testElggPluginManifestGetDonationsPage() { +		$this->assertEqual($this->manifest18->getDonationsPageURL(), 'http://elgg.org/supporter.php'); +		$this->assertEqual($this->manifest17->getDonationsPageURL(), ''); +	}  	public function testElggPluginManifestGetCopyright() {  		$this->assertEqual($this->manifest18->getCopyright(), '(C) Elgg Foundation 2011'); diff --git a/engine/tests/regression/trac_bugs.php b/engine/tests/regression/trac_bugs.php index 26a45ab6a..691433a41 100644 --- a/engine/tests/regression/trac_bugs.php +++ b/engine/tests/regression/trac_bugs.php @@ -202,16 +202,33 @@ class ElggCoreRegressionBugsTest extends ElggCoreUnitTest {  	/**  	 * http://trac.elgg.org/ticket/3210 - Don't remove -s in friendly titles -	 * @todo: http://trac.elgg.org/ticket/2276 - improve char encoding +	 * http://trac.elgg.org/ticket/2276 - improve char encoding  	 */  	public function test_friendly_title() {  		$cases = array( -			'Simple Test' => 'simple-test', -			'Test top-level page' => 'test-top-level-page', -//			'éclair' => 'éclair', -//			'English, Español, and 日本語' => 'english-español-and-日本語' +			// hyphen, underscore and ASCII whitespace replaced by separator, +			// other non-alphanumeric ASCII removed +			"a-a_a a\na\ra\ta\va!a\"a#a\$a%a&a'a(a)a*a+a,a.a/a:a;a<a=a>a?a@a[a\\a]a^a`a{a|a}a~a" +			=> "a-a-a-a-a-a-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + +			// separators trimmed +			"-_ hello _-" => "hello", + +			// accents removed, lower case, other multibyte chars are URL encoded +			"I\xC3\xB1t\xC3\xABrn\xC3\xA2ti\xC3\xB4n\xC3\xA0liz\xC3\xA6ti\xC3\xB8n, AND \xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" +				// Iñtërnâtiônàlizætiøn, AND 日本語 +			=> 'internationalizaetion-and-%E6%97%A5%E6%9C%AC%E8%AA%9E', + +			// some HTML entity replacements +			"Me & You" => 'me-and-you',  		); +		// where available, string is converted to NFC before transliteration +		if (ElggTranslit::hasNormalizerSupport()) { +			$form_d = "A\xCC\x8A"; // A followed by 'COMBINING RING ABOVE' (U+030A) +			$cases[$form_d] = "a"; +		} +  		foreach ($cases as $case => $expected) {  			$friendly_title = elgg_get_friendly_title($case);  			$this->assertIdentical($expected, $friendly_title); diff --git a/engine/tests/test_files/plugin_18/manifest.xml b/engine/tests/test_files/plugin_18/manifest.xml index 9654b6422..5d788616a 100644 --- a/engine/tests/test_files/plugin_18/manifest.xml +++ b/engine/tests/test_files/plugin_18/manifest.xml @@ -6,6 +6,9 @@  	<blurb>A concise description.</blurb>  	<description>A longer, more interesting description.</description>  	<website>http://www.elgg.org/</website> +	<repository>https://github.com/Elgg/Elgg</repository> +	<bugtracker>http://trac.elgg.org</bugtracker> +	<donations>http://elgg.org/supporter.php</donations>  	<copyright>(C) Elgg Foundation 2011</copyright>  	<license>GNU General Public License version 2</license> | 
