diff options
| author | Sem <sembrestels@riseup.net> | 2014-01-22 04:05:47 +0100 | 
|---|---|---|
| committer | Sem <sembrestels@riseup.net> | 2014-01-22 04:05:47 +0100 | 
| commit | 68614b769f4ae4f28c3f395f47b68baba7c48c64 (patch) | |
| tree | 2c5a744a3859d27883f92b72aef9cf81f1a947d0 /engine | |
| parent | 69e2d8c5d8732042c9319aef1fdea45a82b63e42 (diff) | |
| parent | c0295c275d6edbca6c6c8bb51dc199150d0d5fc3 (diff) | |
| download | elgg-68614b769f4ae4f28c3f395f47b68baba7c48c64.tar.gz elgg-68614b769f4ae4f28c3f395f47b68baba7c48c64.tar.bz2  | |
Merge branch 'release/1.8.1'
Diffstat (limited to 'engine')
106 files changed, 2532 insertions, 860 deletions
diff --git a/engine/classes/ElggAccess.php b/engine/classes/ElggAccess.php index 6f8d9bb4b..0aed477fc 100644 --- a/engine/classes/ElggAccess.php +++ b/engine/classes/ElggAccess.php @@ -16,6 +16,7 @@ class ElggAccess {  	 */  	private $ignore_access; +	// @codingStandardsIgnoreStart  	/**  	 * Get current ignore access setting.  	 * @@ -26,6 +27,7 @@ class ElggAccess {  		elgg_deprecated_notice('ElggAccess::get_ignore_access() is deprecated by ElggAccess::getIgnoreAccess()', 1.8);  		return $this->getIgnoreAccess();  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Get current ignore access setting. @@ -36,6 +38,7 @@ class ElggAccess {  		return $this->ignore_access;  	} +	// @codingStandardsIgnoreStart  	/**  	 * Set ignore access.  	 * @@ -49,6 +52,7 @@ class ElggAccess {  		elgg_deprecated_notice('ElggAccess::set_ignore_access() is deprecated by ElggAccess::setIgnoreAccess()', 1.8);  		return $this->setIgnoreAccess($ignore);  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Set ignore access. diff --git a/engine/classes/ElggAnnotation.php b/engine/classes/ElggAnnotation.php index 511b5151f..175e7049d 100644 --- a/engine/classes/ElggAnnotation.php +++ b/engine/classes/ElggAnnotation.php @@ -11,6 +11,9 @@   * @package    Elgg.Core   * @subpackage DataModel.Annotations   * @link       http://docs.elgg.org/DataModel/Annotations + * + * @property string $value_type + * @property string $enabled   */  class ElggAnnotation extends ElggExtender { @@ -56,6 +59,8 @@ class ElggAnnotation extends ElggExtender {  	 * Save this instance  	 *  	 * @return int an object id +	 * +	 * @throws IOException  	 */  	function save() {  		if ($this->id > 0) { diff --git a/engine/classes/ElggAttributeLoader.php b/engine/classes/ElggAttributeLoader.php index 602bb8bae..ffc80b02d 100644 --- a/engine/classes/ElggAttributeLoader.php +++ b/engine/classes/ElggAttributeLoader.php @@ -4,6 +4,9 @@   * Loads ElggEntity attributes from DB or validates those passed in via constructor   *   * @access private + * + * @package    Elgg.Core + * @subpackage DataModel   */  class ElggAttributeLoader { @@ -21,7 +24,7 @@ class ElggAttributeLoader {  		'time_created',  		'time_updated',  		'last_action', -		'enabled' +		'enabled',  	);  	/** @@ -65,9 +68,11 @@ class ElggAttributeLoader {  	public $full_loader = '';  	/** -	 * @param string $class class of object being loaded -	 * @param string $required_type entity type this is being used to populate -	 * @param array $initialized_attrs attributes after initializeAttributes() has been run +	 * Constructor +	 * +	 * @param string $class             class of object being loaded +	 * @param string $required_type     entity type this is being used to populate +	 * @param array  $initialized_attrs attributes after initializeAttributes() has been run  	 * @throws InvalidArgumentException  	 */  	public function __construct($class, $required_type, array $initialized_attrs) { @@ -87,14 +92,33 @@ class ElggAttributeLoader {  		$this->secondary_attr_names = array_diff($all_attr_names, self::$primary_attr_names);  	} +	/** +	 * Get primary attributes missing that are missing +	 * +	 * @param stdClass $row Database row +	 * @return array +	 */  	protected function isMissingPrimaries($row) {  		return array_diff(self::$primary_attr_names, array_keys($row)) !== array();  	} +	/** +	 * Get secondary attributes that are missing +	 * +	 * @param stdClass $row Database row +	 * @return array +	 */  	protected function isMissingSecondaries($row) {  		return array_diff($this->secondary_attr_names, array_keys($row)) !== array();  	} +	/** +	 * Check that the type is correct +	 * +	 * @param stdClass $row Database row +	 * @return void +	 * @throws InvalidClassException +	 */  	protected function checkType($row) {  		if ($row['type'] !== $this->required_type) {  			$msg = elgg_echo('InvalidClassException:NotValidElggStar', array($row['guid'], $this->class)); @@ -148,11 +172,11 @@ class ElggAttributeLoader {  				if (!is_callable($this->primary_loader)) {  					throw new LogicException('Primary attribute loader must be callable');  				} -				if (!$this->requires_access_control) { +				if ($this->requires_access_control) { +					$fetched = (array) call_user_func($this->primary_loader, $row['guid']); +				} else {  					$ignoring_access = elgg_set_ignore_access(); -				} -				$fetched = (array) call_user_func($this->primary_loader, $row['guid']); -				if (!$this->requires_access_control) { +					$fetched = (array) call_user_func($this->primary_loader, $row['guid']);  					elgg_set_ignore_access($ignoring_access);  				}  				if (!$fetched) { @@ -176,6 +200,8 @@ class ElggAttributeLoader {  						// saved, these are stored w/ type "site", but with no sites_entity row. These  						// are probably only created in the unit tests.  						// @todo Don't save vanilla ElggEntities with type "site" + +						$row = $this->filterAddedColumns($row);  						$row['guid'] = (int) $row['guid'];  						return $row;  					} @@ -185,15 +211,38 @@ class ElggAttributeLoader {  			}  		} -		// loading complete: re-check missing and check type -		if (($was_missing_primaries && $this->isMissingPrimaries($row)) -				|| ($was_missing_secondaries && $this->isMissingSecondaries($row))) { -			throw new LogicException('Attribute loaders failed to return proper attributes'); -		} +		$row = $this->filterAddedColumns($row); + +		// Note: If there are still missing attributes, we're running on a 1.7 or earlier schema. We let +		// this pass so the upgrades can run. -		// guid needs to be an int  http://trac.elgg.org/ticket/4111 +		// guid needs to be an int  https://github.com/elgg/elgg/issues/4111  		$row['guid'] = (int) $row['guid'];  		return $row;  	} + +	/** +	 * Filter out keys returned by the query which should not appear in the entity's attributes +	 * +	 * @param array $row All columns from the query +	 * @return array Columns acceptable for the entity's attributes +	 */ +	protected function filterAddedColumns($row) { +		// make an array with keys as acceptable attribute names +		$acceptable_attrs = self::$primary_attr_names; +		array_splice($acceptable_attrs, count($acceptable_attrs), 0, $this->secondary_attr_names); +		$acceptable_attrs = array_combine($acceptable_attrs, $acceptable_attrs); + +		// @todo remove these when #4584 is in place +		$acceptable_attrs['tables_split'] = true; +		$acceptable_attrs['tables_loaded'] = true; + +		foreach ($row as $key => $val) { +			if (!isset($acceptable_attrs[$key])) { +				unset($row[$key]); +			} +		} +		return $row; +	}  } diff --git a/engine/classes/ElggAutoP.php b/engine/classes/ElggAutoP.php index 89d77e583..05842d1b2 100644 --- a/engine/classes/ElggAutoP.php +++ b/engine/classes/ElggAutoP.php @@ -7,6 +7,9 @@   *   * In DIV elements, Ps are only added when there would be at   * least two of them. + *  + * @package    Elgg.Core + * @subpackage Output   */  class ElggAutoP { @@ -51,8 +54,12 @@ class ElggAutoP {  	protected $_alterList = 'article aside blockquote body details div footer header  		section'; +	/** @var string */  	protected $_unique = ''; +	/** +	 * Constructor +	 */  	public function __construct() {  		$this->_blocks = preg_split('@\\s+@', $this->_blocks);  		$this->_descendList = preg_split('@\\s+@', $this->_descendList); @@ -98,25 +105,34 @@ class ElggAutoP {  		$html = str_replace('&', $this->_unique . 'AMP', $html);  		$this->_doc = new DOMDocument(); -	    +  		// parse to DOM, suppressing loadHTML warnings  		// http://www.php.net/manual/en/domdocument.loadhtml.php#95463  		libxml_use_internal_errors(true); +		// Do not load entities. May be unnecessary, better safe than sorry +		$disable_load_entities = libxml_disable_entity_loader(true); +  		if (!$this->_doc->loadHTML("<html><meta http-equiv='content-type' "   				. "content='text/html; charset={$this->encoding}'><body>{$html}</body>"  				. "</html>")) { + +			libxml_disable_entity_loader($disable_load_entities);  			return false;  		} +		libxml_disable_entity_loader($disable_load_entities); +  		$this->_xpath = new DOMXPath($this->_doc);  		// start processing recursively at the BODY element  		$nodeList = $this->_xpath->query('//body[1]'); -		$this->_addParagraphs($nodeList->item(0)); +		$this->addParagraphs($nodeList->item(0));  		// serialize back to HTML  		$html = $this->_doc->saveHTML(); +		// Note: we create <autop> elements, which will later be converted to paragraphs +  		// split AUTOPs into multiples at /\n\n+/  		$html = preg_replace('/(' . $this->_unique . 'NL){2,}/', '</autop><autop>', $html);  		$html = str_replace(array($this->_unique . 'BR', $this->_unique . 'NL', '<br>'),  @@ -126,14 +142,22 @@ class ElggAutoP {  		// re-parse so we can handle new AUTOP elements +		// Do not load entities. May be unnecessary, better safe than sorry +		$disable_load_entities = libxml_disable_entity_loader(true); +  		if (!$this->_doc->loadHTML($html)) { +			libxml_disable_entity_loader($disable_load_entities);  			return false;  		} + +		libxml_disable_entity_loader($disable_load_entities); +  		// must re-create XPath object after DOM load  		$this->_xpath = new DOMXPath($this->_doc);  		// strip AUTOPs that only have comments/whitespace  		foreach ($this->_xpath->query('//autop') as $autop) { +			/* @var DOMElement $autop */  			$hasContent = false;  			if (trim($autop->textContent) !== '') {  				$hasContent = true; @@ -146,17 +170,19 @@ class ElggAutoP {  				}  			}  			if (!$hasContent) { -				// strip w/ preg_replace later (faster than moving nodes out) +				// mark to be later replaced w/ preg_replace (faster than moving nodes out)  				$autop->setAttribute("r", "1");  			}  		} -		// remove a single AUTOP inside certain elements +		// If a DIV contains a single AUTOP, remove it  		foreach ($this->_xpath->query('//div') as $el) { +			/* @var DOMElement $el */  			$autops = $this->_xpath->query('./autop', $el);  			if ($autops->length === 1) { -				// strip w/ preg_replace later (faster than moving nodes out) -				$autops->item(0)->setAttribute("r", "1"); +				$firstAutop = $autops->item(0); +				/* @var DOMElement $firstAutop */ +				$firstAutop->setAttribute("r", "1");  			}  		} @@ -182,15 +208,16 @@ class ElggAutoP {  	/**  	 * Add P and BR elements as necessary  	 * -	 * @param DOMElement $el +	 * @param DOMElement $el DOM element +	 * @return void  	 */ -	protected function _addParagraphs(DOMElement $el) { -		// no need to recurse, just queue up +	protected function addParagraphs(DOMElement $el) { +		// no need to call recursively, just queue up  		$elsToProcess = array($el);  		$inlinesToProcess = array();  		while ($el = array_shift($elsToProcess)) {  			// if true, we can alter all child nodes, if not, we'll just call -			// _addParagraphs on each element in the descendInto list +			// addParagraphs on each element in the descendInto list  			$alterInline = in_array($el->nodeName, $this->_alterList);  			// inside affected elements, we want to trim leading whitespace from @@ -216,16 +243,16 @@ class ElggAutoP {  				$isElement = ($node->nodeType === XML_ELEMENT_NODE);  				if ($isElement) { -					$elName = $node->nodeName; +					$isBlock = in_array($node->nodeName, $this->_blocks); +				} else { +					$isBlock = false;  				} -				$isBlock = ($isElement && in_array($elName, $this->_blocks));  				if ($alterInline) { -					$isInline = $isElement && ! $isBlock;  					$isText = ($node->nodeType === XML_TEXT_NODE);  					$isLastInline = (! $node->nextSibling -								   || ($node->nextSibling->nodeType === XML_ELEMENT_NODE -									   && in_array($node->nextSibling->nodeName, $this->_blocks))); +							|| ($node->nextSibling->nodeType === XML_ELEMENT_NODE +								&& in_array($node->nextSibling->nodeName, $this->_blocks)));  					if ($isElement) {  						$isFollowingBr = ($node->nodeName === 'br');  					} @@ -258,7 +285,7 @@ class ElggAutoP {  					if ($isBlock) {  						if (in_array($node->nodeName, $this->_descendList)) {  							$elsToProcess[] = $node; -							//$this->_addParagraphs($node); +							//$this->addParagraphs($node);  						}  					}  					$openP = true; diff --git a/engine/classes/ElggBatch.php b/engine/classes/ElggBatch.php index c1a77a0d9..d810ea066 100644 --- a/engine/classes/ElggBatch.php +++ b/engine/classes/ElggBatch.php @@ -150,6 +150,20 @@ class ElggBatch  	private $incrementOffset = true;  	/** +	 * Entities that could not be instantiated during a fetch +	 * +	 * @var stdClass[] +	 */ +	private $incompleteEntities = array(); + +	/** +	 * Total number of incomplete entities fetched +	 * +	 * @var int +	 */ +	private $totalIncompletes = 0; + +	/**  	 * Batches operations on any elgg_get_*() or compatible function that supports  	 * an options array.  	 * @@ -222,16 +236,22 @@ class ElggBatch  	}  	/** +	 * Tell the process that an entity was incomplete during a fetch +	 * +	 * @param stdClass $row +	 * +	 * @access private +	 */ +	public function reportIncompleteEntity(stdClass $row) { +		$this->incompleteEntities[] = $row; +	} + +	/**  	 * Fetches the next chunk of results  	 *  	 * @return bool  	 */  	private function getNextResultsChunk() { -		// reset memory caches after first chunk load -		if ($this->chunkIndex > 0) { -			global $DB_QUERY_CACHE, $ENTITY_CACHE; -			$DB_QUERY_CACHE = $ENTITY_CACHE = array(); -		}  		// always reset results.  		$this->results = array(); @@ -265,27 +285,47 @@ class ElggBatch  		if ($this->incrementOffset) {  			$offset = $this->offset + $this->retrievedResults;  		} else { -			$offset = $this->offset; +			$offset = $this->offset + $this->totalIncompletes;  		}  		$current_options = array(  			'limit' => $limit, -			'offset' => $offset +			'offset' => $offset, +			'__ElggBatch' => $this,  		);  		$options = array_merge($this->options, $current_options); -		$getter = $this->getter; -		if (is_string($getter)) { -			$this->results = $getter($options); -		} else { -			$this->results = call_user_func_array($getter, array($options)); +		$this->incompleteEntities = array(); +		$this->results = call_user_func_array($this->getter, array($options)); + +		$num_results = count($this->results); +		$num_incomplete = count($this->incompleteEntities); + +		$this->totalIncompletes += $num_incomplete; + +		if ($this->incompleteEntities) { +			// pad the front of the results with nulls representing the incompletes +			array_splice($this->results, 0, 0, array_pad(array(), $num_incomplete, null)); +			// ...and skip past them +			reset($this->results); +			for ($i = 0; $i < $num_incomplete; $i++) { +				next($this->results); +			}  		}  		if ($this->results) {  			$this->chunkIndex++; -			$this->resultIndex = 0; -			$this->retrievedResults += count($this->results); + +			// let the system know we've jumped past the nulls +			$this->resultIndex = $num_incomplete; + +			$this->retrievedResults += ($num_results + $num_incomplete); +			if ($num_results == 0) { +				// This fetch was *all* incompletes! We need to fetch until we can either +				// offer at least one row to iterate over, or give up. +				return $this->getNextResultsChunk(); +			}  			return true;  		} else {  			return false; @@ -296,7 +336,8 @@ class ElggBatch  	 * Increment the offset from the original options array? Setting to  	 * false is required for callbacks that delete rows.  	 * -	 * @param bool $increment +	 * @param bool $increment Set to false when deleting data +	 * @return void  	 */  	public function setIncrementOffset($increment = true) {  		$this->incrementOffset = (bool) $increment; diff --git a/engine/classes/ElggCache.php b/engine/classes/ElggCache.php index 4317f4be9..909eab39b 100644 --- a/engine/classes/ElggCache.php +++ b/engine/classes/ElggCache.php @@ -21,6 +21,7 @@ abstract class ElggCache implements ArrayAccess {  		$this->variables = array();  	} +	// @codingStandardsIgnoreStart  	/**  	 * Set a cache variable.  	 * @@ -35,6 +36,7 @@ abstract class ElggCache implements ArrayAccess {  		elgg_deprecated_notice('ElggCache::set_variable() is deprecated by ElggCache::setVariable()', 1.8);  		$this->setVariable($variable, $value);  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Set a cache variable. @@ -52,6 +54,7 @@ abstract class ElggCache implements ArrayAccess {  		$this->variables[$variable] = $value;  	} +	// @codingStandardsIgnoreStart  	/**  	 * Get variables for this cache.  	 * @@ -65,6 +68,7 @@ abstract class ElggCache implements ArrayAccess {  		elgg_deprecated_notice('ElggCache::get_variable() is deprecated by ElggCache::getVariable()', 1.8);  		return $this->getVariable($variable);  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Get variables for this cache. diff --git a/engine/classes/ElggCrypto.php b/engine/classes/ElggCrypto.php new file mode 100644 index 000000000..317d371e4 --- /dev/null +++ b/engine/classes/ElggCrypto.php @@ -0,0 +1,208 @@ +<?php +/** + * ElggCrypto + * + * @package    Elgg.Core + * @subpackage Crypto + * + * @access private + */ +class ElggCrypto { + +	/** +	 * Character set for temp passwords (no risk of embedded profanity/glyphs that look similar) +	 */ +	const CHARS_PASSWORD = 'bcdfghjklmnpqrstvwxyz2346789'; + +	/** +	 * Generate a string of highly randomized bytes (over the full 8-bit range). +	 * +	 * @param int $length Number of bytes needed +	 * @return string Random bytes +	 * +	 * @author George Argyros <argyros.george@gmail.com> +	 * @copyright 2012, George Argyros. All rights reserved. +	 * @license Modified BSD +	 * @link https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP/blob/master/srand.php Original +	 * +	 * Redistribution and use in source and binary forms, with or without +	 * modification, are permitted provided that the following conditions are met: +	 *    * Redistributions of source code must retain the above copyright +	 *      notice, this list of conditions and the following disclaimer. +	 *    * Redistributions in binary form must reproduce the above copyright +	 *      notice, this list of conditions and the following disclaimer in the +	 *      documentation and/or other materials provided with the distribution. +	 *    * Neither the name of the <organization> nor the +	 *      names of its contributors may be used to endorse or promote products +	 *      derived from this software without specific prior written permission. +	 * +	 * 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 GEORGE ARGYROS 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. +	 */ +	public function getRandomBytes($length) { +		/** +		 * Our primary choice for a cryptographic strong randomness function is +		 * openssl_random_pseudo_bytes. +		 */ +		$SSLstr = '4'; // http://xkcd.com/221/ +		if (function_exists('openssl_random_pseudo_bytes') +				&& (version_compare(PHP_VERSION, '5.3.4') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) { +			$SSLstr = openssl_random_pseudo_bytes($length, $strong); +			if ($strong) { +				return $SSLstr; +			} +		} + +		/** +		 * If mcrypt extension is available then we use it to gather entropy from +		 * the operating system's PRNG. This is better than reading /dev/urandom +		 * directly since it avoids reading larger blocks of data than needed. +		 * Older versions of mcrypt_create_iv may be broken or take too much time +		 * to finish so we only use this function with PHP 5.3.7 and above. +		 * @see https://bugs.php.net/bug.php?id=55169 +		 */ +		if (function_exists('mcrypt_create_iv') +				&& (version_compare(PHP_VERSION, '5.3.7') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) { +			$str = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); +			if ($str !== false) { +				return $str; +			} +		} + +		/** +		 * No build-in crypto randomness function found. We collect any entropy +		 * available in the PHP core PRNGs along with some filesystem info and memory +		 * stats. To make this data cryptographically strong we add data either from +		 * /dev/urandom or if its unavailable, we gather entropy by measuring the +		 * time needed to compute a number of SHA-1 hashes. +		 */ +		$str = ''; +		$bits_per_round = 2; // bits of entropy collected in each clock drift round +		$msec_per_round = 400; // expected running time of each round in microseconds +		$hash_len = 20; // SHA-1 Hash length +		$total = $length; // total bytes of entropy to collect + +		$handle = @fopen('/dev/urandom', 'rb'); +		if ($handle && function_exists('stream_set_read_buffer')) { +			@stream_set_read_buffer($handle, 0); +		} + +		do { +			$bytes = ($total > $hash_len) ? $hash_len : $total; +			$total -= $bytes; + +			//collect any entropy available from the PHP system and filesystem +			$entropy = rand() . uniqid(mt_rand(), true) . $SSLstr; +			$entropy .= implode('', @fstat(@fopen(__FILE__, 'r'))); +			$entropy .= memory_get_usage() . getmypid(); +			$entropy .= serialize($_ENV) . serialize($_SERVER); +			if (function_exists('posix_times')) { +				$entropy .= serialize(posix_times()); +			} +			if (function_exists('zend_thread_id')) { +				$entropy .= zend_thread_id(); +			} + +			if ($handle) { +				$entropy .= @fread($handle, $bytes); +			} else { +				// Measure the time that the operations will take on average +				for ($i = 0; $i < 3; $i++) { +					$c1 = microtime(true); +					$var = sha1(mt_rand()); +					for ($j = 0; $j < 50; $j++) { +						$var = sha1($var); +					} +					$c2 = microtime(true); +					$entropy .= $c1 . $c2; +				} + +				// Based on the above measurement determine the total rounds +				// in order to bound the total running time. +				$rounds = (int) ($msec_per_round * 50 / (int) (($c2 - $c1) * 1000000)); + +				// Take the additional measurements. On average we can expect +				// at least $bits_per_round bits of entropy from each measurement. +				$iter = $bytes * (int) (ceil(8 / $bits_per_round)); + +				for ($i = 0; $i < $iter; $i++) { +					$c1 = microtime(); +					$var = sha1(mt_rand()); +					for ($j = 0; $j < $rounds; $j++) { +						$var = sha1($var); +					} +					$c2 = microtime(); +					$entropy .= $c1 . $c2; +				} +			} + +			// We assume sha1 is a deterministic extractor for the $entropy variable. +			$str .= sha1($entropy, true); + +		} while ($length > strlen($str)); + +		if ($handle) { +			@fclose($handle); +		} + +		return substr($str, 0, $length); +	} + +	/** +	 * Generate a random string of specified length. +	 * +	 * Uses supplied character list for generating the new string. +	 * If no character list provided - uses Base64 URL character set. +	 * +	 * @param int         $length Desired length of the string +	 * @param string|null $chars  Characters to be chosen from randomly. If not given, the Base64 URL +	 *                            charset will be used. +	 * +	 * @return string The random string +	 * +	 * @throws InvalidArgumentException +	 * +	 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) +	 * @license   http://framework.zend.com/license/new-bsd New BSD License +	 * +	 * @see https://github.com/zendframework/zf2/blob/master/library/Zend/Math/Rand.php#L179 +	 */ +	public static function getRandomString($length, $chars = null) { +		if ($length < 1) { +			throw new InvalidArgumentException('Length should be >= 1'); +		} + +		if (empty($chars)) { +			$numBytes = ceil($length * 0.75); +			$bytes    = self::getRandomBytes($numBytes); +			$string = substr(rtrim(base64_encode($bytes), '='), 0, $length); + +			// Base64 URL +			return strtr($string, '+/', '-_'); +		} + +		$listLen = strlen($chars); + +		if ($listLen == 1) { +			return str_repeat($chars, $length); +		} + +		$bytes  = self::getRandomBytes($length); +		$pos    = 0; +		$result = ''; +		for ($i = 0; $i < $length; $i++) { +			$pos     = ($pos + ord($bytes[$i])) % $listLen; +			$result .= $chars[$pos]; +		} + +		return $result; +	} +} diff --git a/engine/classes/ElggData.php b/engine/classes/ElggData.php index 3470ee1cf..4f843cde4 100644 --- a/engine/classes/ElggData.php +++ b/engine/classes/ElggData.php @@ -5,6 +5,9 @@   *   * @package    Elgg.Core   * @subpackage DataModel + * + * @property int $owner_guid + * @property int $time_created   */  abstract class ElggData implements  	Loggable,	// Can events related to this object class be logged @@ -23,6 +26,7 @@ abstract class ElggData implements  	 */  	protected $attributes = array(); +	// @codingStandardsIgnoreStart  	/**  	 * Initialise the attributes array.  	 * @@ -33,16 +37,15 @@ abstract class ElggData implements  	 *                        Passing false returns false.  Core constructors always pass false.  	 *                        Does nothing either way since attributes are initialized by the time  	 *                        this is called. -	 * @return false|void False is +	 * @return void  	 * @deprecated 1.8 Use initializeAttributes()  	 */  	protected function initialise_attributes($pre18_api = true) {  		if ($pre18_api) {  			elgg_deprecated_notice('initialise_attributes() is deprecated by initializeAttributes()', 1.8); -		} else { -			return false;  		}  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Initialize the attributes array. @@ -111,7 +114,7 @@ abstract class ElggData implements  	 * @param string $name  The attribute to set  	 * @param mixed  $value The value to set it to  	 * -	 * @return The success of your set funtion? +	 * @return bool The success of your set function?  	 */  	abstract protected function set($name, $value); @@ -195,7 +198,7 @@ abstract class ElggData implements  	 *  	 * @see Iterator::current()  	 * -	 * @return void +	 * @return mixed  	 */  	public function current() {  		return current($this->attributes); @@ -206,7 +209,7 @@ abstract class ElggData implements  	 *  	 * @see Iterator::key()  	 * -	 * @return void +	 * @return string  	 */  	public function key() {  		return key($this->attributes); @@ -228,7 +231,7 @@ abstract class ElggData implements  	 *  	 * @see Iterator::valid()  	 * -	 * @return void +	 * @return bool  	 */  	public function valid() {  		return $this->valid; @@ -266,12 +269,13 @@ abstract class ElggData implements  	 *  	 * @param mixed $key Name  	 * -	 * @return void +	 * @return mixed  	 */  	public function offsetGet($key) {  		if (array_key_exists($key, $this->attributes)) {  			return $this->attributes[$key];  		} +		return null;  	}  	/** diff --git a/engine/classes/ElggDiskFilestore.php b/engine/classes/ElggDiskFilestore.php index 7aace43ba..6e2354012 100644 --- a/engine/classes/ElggDiskFilestore.php +++ b/engine/classes/ElggDiskFilestore.php @@ -60,6 +60,7 @@ class ElggDiskFilestore extends ElggFilestore {  		$path = substr($fullname, 0, $ls);  		$name = substr($fullname, $ls); +		// @todo $name is unused, remove it or do we need to fix something?  		// Try and create the directory  		try { @@ -108,7 +109,7 @@ class ElggDiskFilestore extends ElggFilestore {  	 *  	 * @param resource $f      File pointer resource  	 * @param int      $length The number of bytes to read -	 * @param inf      $offset The number of bytes to start after +	 * @param int      $offset The number of bytes to start after  	 *  	 * @return mixed Contents of file or false on fail.  	 */ @@ -193,11 +194,14 @@ class ElggDiskFilestore extends ElggFilestore {  	}  	/** -	 * Returns the filename as saved on disk for an ElggFile object +	 * Get the filename as saved on disk for an ElggFile object +	 * +	 * Returns an empty string if no filename set  	 *  	 * @param ElggFile $file File object  	 *  	 * @return string The full path of where the file is stored +	 * @throws InvalidParameterException  	 */  	public function getFilenameOnFilestore(ElggFile $file) {  		$owner_guid = $file->getOwnerGuid(); @@ -211,7 +215,12 @@ class ElggDiskFilestore extends ElggFilestore {  			throw new InvalidParameterException($msg);  		} -		return $this->dir_root . $this->makefileMatrix($owner_guid) . $file->getFilename(); +		$filename = $file->getFilename(); +		if (!$filename) { +			return ''; +		} + +		return $this->dir_root . $this->makeFileMatrix($owner_guid) . $filename;  	}  	/** @@ -219,7 +228,7 @@ class ElggDiskFilestore extends ElggFilestore {  	 *  	 * @param ElggFile $file File object  	 * -	 * @return mixed +	 * @return string  	 */  	public function grabFile(ElggFile $file) {  		return file_get_contents($file->getFilenameOnFilestore()); @@ -233,6 +242,9 @@ class ElggDiskFilestore extends ElggFilestore {  	 * @return bool  	 */  	public function exists(ElggFile $file) { +		if (!$file->getFilename()) { +			return false; +		}  		return file_exists($this->getFilenameOnFilestore($file));  	} @@ -246,12 +258,13 @@ class ElggDiskFilestore extends ElggFilestore {  	 */  	public function getSize($prefix = '', $container_guid) {  		if ($container_guid) { -			return get_dir_size($this->dir_root . $this->makefileMatrix($container_guid) . $prefix); +			return get_dir_size($this->dir_root . $this->makeFileMatrix($container_guid) . $prefix);  		} else {  			return false;  		}  	} +	// @codingStandardsIgnoreStart  	/**  	 * Create a directory $dirroot  	 * @@ -266,6 +279,7 @@ class ElggDiskFilestore extends ElggFilestore {  		return $this->makeDirectoryRoot($dirroot);  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Create a directory $dirroot @@ -285,6 +299,7 @@ class ElggDiskFilestore extends ElggFilestore {  		return true;  	} +	// @codingStandardsIgnoreStart  	/**  	 * Multibyte string tokeniser.  	 * @@ -315,30 +330,31 @@ class ElggDiskFilestore extends ElggFilestore {  		} else {  			return str_split($string);  		} - -		return false;  	} +	// @codingStandardsIgnoreEnd +	// @codingStandardsIgnoreStart  	/**  	 * Construct a file path matrix for an entity.  	 *  	 * @param int $identifier The guide of the entity to store the data under.  	 * -	 * @return str The path where the entity's data will be stored. +	 * @return string The path where the entity's data will be stored.  	 * @deprecated 1.8 Use ElggDiskFilestore::makeFileMatrix()  	 */  	protected function make_file_matrix($identifier) {  		elgg_deprecated_notice('ElggDiskFilestore::make_file_matrix() is deprecated by ::makeFileMatrix()', 1.8); -		return $this->makefileMatrix($identifier); +		return $this->makeFileMatrix($identifier);  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Construct a file path matrix for an entity.  	 *  	 * @param int $guid The guide of the entity to store the data under.  	 * -	 * @return str The path where the entity's data will be stored. +	 * @return string The path where the entity's data will be stored.  	 */  	protected function makeFileMatrix($guid) {  		$entity = get_entity($guid); @@ -352,6 +368,7 @@ class ElggDiskFilestore extends ElggFilestore {  		return "$time_created/$entity->guid/";  	} +	// @codingStandardsIgnoreStart  	/**  	 * Construct a filename matrix.  	 * @@ -363,13 +380,14 @@ class ElggDiskFilestore extends ElggFilestore {  	 *  	 * @param int $guid The entity to contrust a matrix for  	 * -	 * @return str The +	 * @return string The  	 */  	protected function user_file_matrix($guid) {  		elgg_deprecated_notice('ElggDiskFilestore::user_file_matrix() is deprecated by ::makeFileMatrix()', 1.8);  		return $this->makeFileMatrix($guid);  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Returns a list of attributes to save to the database when saving diff --git a/engine/classes/ElggEntity.php b/engine/classes/ElggEntity.php index 929abceb2..a563f6fad 100644 --- a/engine/classes/ElggEntity.php +++ b/engine/classes/ElggEntity.php @@ -24,7 +24,7 @@   *   * @package    Elgg.Core   * @subpackage DataModel.Entities - *  + *   * @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)   * @property int    $guid           The unique identifier for this entity (read only) @@ -34,6 +34,7 @@   * @property int    $access_id      Specifies the visibility level of this entity   * @property int    $time_created   A UNIX timestamp of when the entity was created (read-only, set on first save)   * @property int    $time_updated   A UNIX timestamp of when the entity was last updated (automatically updated on save) + * @property-read string $enabled   */  abstract class ElggEntity extends ElggData implements  	Notable,    // Calendar interface @@ -351,8 +352,8 @@ abstract class ElggEntity extends ElggData implements  					'limit' => 0  				);  				// @todo in 1.9 make this return false if can't add metadata -				// http://trac.elgg.org/ticket/4520 -				//  +				// https://github.com/elgg/elgg/issues/4520 +				//  				// need to remove access restrictions right now to delete  				// because this is the expected behavior  				$ia = elgg_set_ignore_access(true); @@ -374,12 +375,11 @@ abstract class ElggEntity extends ElggData implements  			}  			return $result; -		} - -		// unsaved entity. store in temp array -		// returning single entries instead of an array of 1 element is decided in -		// getMetaData(), just like pulling from the db. -		else { +		} else { +			// unsaved entity. store in temp array +			// returning single entries instead of an array of 1 element is decided in +			// getMetaData(), just like pulling from the db. +			//  			// if overwrite, delete first  			if (!$multiple || !isset($this->temp_metadata[$name])) {  				$this->temp_metadata[$name] = array(); @@ -940,7 +940,7 @@ abstract class ElggEntity extends ElggData implements  	 * @param ElggMetadata $metadata  The piece of metadata to specifically check  	 * @param int          $user_guid The user GUID, optionally (default: logged in user)  	 * -	 * @return true|false +	 * @return bool  	 */  	function canEditMetadata($metadata = null, $user_guid = 0) {  		return can_edit_entity_metadata($this->getGUID(), $user_guid, $metadata); @@ -964,7 +964,7 @@ abstract class ElggEntity extends ElggData implements  	 *  	 * @tip Can be overridden by registering for the permissions_check:comment,  	 * <entity type> plugin hook. -	 *  +	 *  	 * @param int $user_guid User guid (default is logged in user)  	 *  	 * @return bool @@ -1270,15 +1270,23 @@ abstract class ElggEntity extends ElggData implements  	public function save() {  		$guid = $this->getGUID();  		if ($guid > 0) { -			cache_entity($this); -			return update_entity( +			// See #5600. This ensures the lower level can_edit_entity() check will use a +			// fresh entity from the DB so it sees the persisted owner_guid +			_elgg_disable_caching_for_entity($guid); + +			$ret = update_entity(  				$guid,  				$this->get('owner_guid'),  				$this->get('access_id'),  				$this->get('container_guid'),  				$this->get('time_created')  			); + +			_elgg_enable_caching_for_entity($guid); +			_elgg_cache_entity($this); + +			return $ret;  		} else {  			// Create a new entity (nb: using attribute array directly  			// 'cos set function does something special!) @@ -1320,7 +1328,7 @@ abstract class ElggEntity extends ElggData implements  			$this->attributes['subtype'] = get_subtype_id($this->attributes['type'],  				$this->attributes['subtype']); -			cache_entity($this); +			_elgg_cache_entity($this);  			return $this->attributes['guid'];  		} @@ -1357,12 +1365,12 @@ abstract class ElggEntity extends ElggData implements  				$this->attributes['tables_loaded']++;  			} -			// guid needs to be an int  http://trac.elgg.org/ticket/4111 +			// guid needs to be an int  https://github.com/elgg/elgg/issues/4111  			$this->attributes['guid'] = (int)$this->attributes['guid'];  			// Cache object handle  			if ($this->attributes['guid']) { -				cache_entity($this); +				_elgg_cache_entity($this);  			}  			return true; @@ -1668,9 +1676,11 @@ abstract class ElggEntity extends ElggData implements  	/**  	 * Import data from an parsed ODD xml data array.  	 * -	 * @param array $data XML data +	 * @param ODD $data XML data  	 *  	 * @return true +	 * +	 * @throws InvalidParameterException  	 */  	public function import(ODD $data) {  		if (!($data instanceof ODDEntity)) { @@ -1732,8 +1742,6 @@ abstract class ElggEntity extends ElggData implements  	 * @return array  	 */  	public function getTags($tag_names = NULL) { -		global $CONFIG; -  		if ($tag_names && !is_array($tag_names)) {  			$tag_names = array($tag_names);  		} diff --git a/engine/classes/ElggExtender.php b/engine/classes/ElggExtender.php index d94bad837..25aba354f 100644 --- a/engine/classes/ElggExtender.php +++ b/engine/classes/ElggExtender.php @@ -171,7 +171,7 @@ abstract class ElggExtender extends ElggData {  	public function export() {  		$uuid = get_uuid_from_object($this); -		$meta = new ODDMetadata($uuid, guid_to_uuid($this->entity_guid), $this->attributes['name'], +		$meta = new ODDMetaData($uuid, guid_to_uuid($this->entity_guid), $this->attributes['name'],  			$this->attributes['value'], $this->attributes['type'], guid_to_uuid($this->owner_guid));  		$meta->setAttribute('published', date("r", $this->time_created)); diff --git a/engine/classes/ElggFile.php b/engine/classes/ElggFile.php index f21621ffd..23080834b 100644 --- a/engine/classes/ElggFile.php +++ b/engine/classes/ElggFile.php @@ -93,6 +93,7 @@ class ElggFile extends ElggObject {  			$container_guid = $this->container_guid;  		}  		$fs = $this->getFilestore(); +		// @todo add getSize() to ElggFilestore  		return $fs->getSize($prefix, $container_guid);  	} @@ -127,9 +128,11 @@ class ElggFile extends ElggObject {  	 * @param mixed $default A default. Useful to pass what the browser thinks it is.  	 * @since 1.7.12  	 * +	 * @note If $file is provided, this may be called statically +	 *  	 * @return mixed Detected type on success, false on failure.  	 */ -	static function detectMimeType($file = null, $default = null) { +	public function detectMimeType($file = null, $default = null) {  		if (!$file) {  			if (isset($this) && $this->filename) {  				$file = $this->filename; @@ -178,6 +181,8 @@ class ElggFile extends ElggObject {  	 * @param string $mode Either read/write/append  	 *  	 * @return resource File handler +	 * +	 * @throws IOException|InvalidParameterException  	 */  	public function open($mode) {  		if (!$this->getFilename()) { @@ -270,9 +275,14 @@ class ElggFile extends ElggObject {  	 */  	public function delete() {  		$fs = $this->getFilestore(); -		if ($fs->delete($this)) { -			return parent::delete(); +		 +		$result = $fs->delete($this); +		 +		if ($this->getGUID() && $result) { +			$result = parent::delete();  		} +		 +		return $result;  	}  	/** @@ -285,6 +295,7 @@ class ElggFile extends ElggObject {  	public function seek($position) {  		$fs = $this->getFilestore(); +		// @todo add seek() to ElggFilestore  		return $fs->seek($this->handle, $position);  	} @@ -347,6 +358,8 @@ class ElggFile extends ElggObject {  	 * a filestore as recorded in metadata or the system default.  	 *  	 * @return ElggFilestore +	 * +	 * @throws ClassNotFoundException  	 */  	protected function getFilestore() {  		// Short circuit if already set. @@ -359,7 +372,6 @@ class ElggFile extends ElggObject {  		// need to get all filestore::* metadata because the rest are "parameters" that  		// get passed to filestore::setParameters()  		if ($this->guid) { -			$db_prefix = elgg_get_config('dbprefix');  			$options = array(  				'guid' => $this->guid,  				'where' => array("n.string LIKE 'filestore::%'"), @@ -388,6 +400,7 @@ class ElggFile extends ElggObject {  			$this->filestore = new $filestore();  			$this->filestore->setParameters($parameters); +			// @todo explain why $parameters will always be set here (PhpStorm complains)  		}  		// this means the entity hasn't been saved so fallback to default diff --git a/engine/classes/ElggFileCache.php b/engine/classes/ElggFileCache.php index 34178d452..94143f777 100644 --- a/engine/classes/ElggFileCache.php +++ b/engine/classes/ElggFileCache.php @@ -13,6 +13,8 @@ class ElggFileCache extends ElggCache {  	 * @param string $cache_path The cache path.  	 * @param int    $max_age    Maximum age in seconds, 0 if no limit.  	 * @param int    $max_size   Maximum size of cache in seconds, 0 if no limit. +	 * +	 * @throws ConfigurationException  	 */  	function __construct($cache_path, $max_age = 0, $max_size = 0) {  		$this->setVariable("cache_path", $cache_path); @@ -24,6 +26,7 @@ class ElggFileCache extends ElggCache {  		}  	} +	// @codingStandardsIgnoreStart  	/**  	 * Create and return a handle to a file.  	 * @@ -39,6 +42,7 @@ class ElggFileCache extends ElggCache {  		return $this->createFile($filename, $rw);  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Create and return a handle to a file. @@ -70,6 +74,7 @@ class ElggFileCache extends ElggCache {  		return fopen($path . $filename, $rw);  	} +	// @codingStandardsIgnoreStart  	/**  	 * Create a sanitised filename for the file.  	 * @@ -84,6 +89,7 @@ class ElggFileCache extends ElggCache {  		return $filename;  	} +	// @codingStandardsIgnoreEnd  	/**  	 * Create a sanitised filename for the file. diff --git a/engine/classes/ElggGroup.php b/engine/classes/ElggGroup.php index ea257f368..7e69b7a84 100644 --- a/engine/classes/ElggGroup.php +++ b/engine/classes/ElggGroup.php @@ -32,7 +32,7 @@ class ElggGroup extends ElggEntity  	 * @param mixed $guid If an int, load that GUID.  	 * 	If an entity table db row, then will load the rest of the data.  	 * -	 * @throws Exception if there was a problem creating the group. +	 * @throws IOException|InvalidParameterException if there was a problem creating the group.  	 */  	function __construct($guid = null) {  		$this->initializeAttributes(); @@ -48,21 +48,18 @@ class ElggGroup extends ElggEntity  					$msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid));  					throw new IOException($msg);  				} - -			// Is $guid is an ElggGroup? Use a copy constructor  			} else if ($guid instanceof ElggGroup) { +				// $guid is an ElggGroup so this is a copy constructor  				elgg_deprecated_notice('This type of usage of the ElggGroup constructor was deprecated. Please use the clone method.', 1.7);  				foreach ($guid->attributes as $key => $value) {  					$this->attributes[$key] = $value;  				} - -			// Is this is an ElggEntity but not an ElggGroup = ERROR!  			} else if ($guid instanceof ElggEntity) { +				// @todo why separate from else  				throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggGroup')); - -			// Is it a GUID  			} else if (is_numeric($guid)) { +				// $guid is a GUID so load entity  				if (!$this->load($guid)) {  					throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid)));  				} @@ -220,6 +217,7 @@ class ElggGroup extends ElggEntity  	 * @return array|false  	 */  	public function getObjects($subtype = "", $limit = 10, $offset = 0) { +		// @todo are we deprecating this method, too?  		return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false);  	} @@ -233,6 +231,7 @@ class ElggGroup extends ElggEntity  	 * @return array|false  	 */  	public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0) { +		// @todo are we deprecating this method, too?  		return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false);  	} @@ -244,6 +243,7 @@ class ElggGroup extends ElggEntity  	 * @return array|false  	 */  	public function countObjects($subtype = "") { +		// @todo are we deprecating this method, too?  		return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", 10, 0, true);  	} @@ -284,7 +284,7 @@ class ElggGroup extends ElggEntity  	 *  	 * @return bool  	 */ -	public function isMember($user = 0) { +	public function isMember($user = null) {  		if (!($user instanceof ElggUser)) {  			$user = elgg_get_logged_in_user_entity();  		} @@ -335,7 +335,7 @@ class ElggGroup extends ElggEntity  		$this->attributes = $attrs;  		$this->attributes['tables_loaded'] = 2; -		cache_entity($this); +		_elgg_cache_entity($this);  		return true;  	} @@ -352,7 +352,12 @@ class ElggGroup extends ElggEntity  		}  		// Now save specific stuff -		return create_group_entity($this->get('guid'), $this->get('name'), $this->get('description')); + +		_elgg_disable_caching_for_entity($this->guid); +		$ret = create_group_entity($this->get('guid'), $this->get('name'), $this->get('description')); +		_elgg_enable_caching_for_entity($this->guid); + +		return $ret;  	}  	// EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// diff --git a/engine/classes/ElggLRUCache.php b/engine/classes/ElggLRUCache.php new file mode 100644 index 000000000..f51af2ed7 --- /dev/null +++ b/engine/classes/ElggLRUCache.php @@ -0,0 +1,181 @@ +<?php + +/** + * Least Recently Used Cache + * + * A fixed sized cache that removes the element used last when it reaches its + * size limit. + *  + * Based on https://github.com/cash/LRUCache + *  + * @access private + *  + * @package    Elgg.Core + * @subpackage Cache + */ +class ElggLRUCache implements ArrayAccess { +	/** @var int */ +	protected $maximumSize; + +	/** +	 * The front of the array contains the LRU element +	 * +	 * @var array +	 */ +	protected $data = array(); + +	/** +	 * Create a LRU Cache +	 * +	 * @param int $size The size of the cache +	 * @throws InvalidArgumentException +	 */ +	public function __construct($size) { +		if (!is_int($size) || $size <= 0) { +			throw new InvalidArgumentException(); +		} +		$this->maximumSize = $size; +	} + +	/** +	 * Get the value cached with this key +	 * +	 * @param int|string $key     The key. Strings that are ints are cast to ints. +	 * @param mixed      $default The value to be returned if key not found. (Optional) +	 * @return mixed +	 */ +	public function get($key, $default = null) { +		if (isset($this->data[$key])) { +			$this->recordAccess($key); +			return $this->data[$key]; +		} else { +			return $default; +		} +	} + +	/** +	 * Add something to the cache +	 * +	 * @param int|string $key   The key. Strings that are ints are cast to ints. +	 * @param mixed      $value The value to cache +	 * @return void +	 */ +	public function set($key, $value) { +		if (isset($this->data[$key])) { +			$this->data[$key] = $value; +			$this->recordAccess($key); +		} else { +			$this->data[$key] = $value; +			if ($this->size() > $this->maximumSize) { +				// remove least recently used element (front of array) +				reset($this->data); +				unset($this->data[key($this->data)]); +			} +		} +	} + +	/** +	 * Get the number of elements in the cache +	 * +	 * @return int +	 */ +	public function size() { +		return count($this->data); +	} + +	/** +	 * Does the cache contain an element with this key +	 * +	 * @param int|string $key The key +	 * @return boolean +	 */ +	public function containsKey($key) { +		return isset($this->data[$key]); +	} + +	/** +	 * Remove the element with this key. +	 * +	 * @param int|string $key The key +	 * @return mixed Value or null if not set +	 */ +	public function remove($key) { +		if (isset($this->data[$key])) { +			$value = $this->data[$key]; +			unset($this->data[$key]); +			return $value; +		} else { +			return null; +		} +	} + +	/** +	 * Clear the cache +	 *  +	 * @return void +	 */ +	public function clear() { +		$this->data = array(); +	} + +	/** +	 * Moves the element from current position to end of array +	 *  +	 * @param int|string $key The key +	 * @return void +	 */ +	protected function recordAccess($key) { +		$value = $this->data[$key]; +		unset($this->data[$key]); +		$this->data[$key] = $value; +	} + +	/** +	 * Assigns a value for the specified key +	 * +	 * @see ArrayAccess::offsetSet() +	 * +	 * @param int|string $key   The key to assign the value to. +	 * @param mixed      $value The value to set. +	 * @return void +	 */ +	public function offsetSet($key, $value) { +		$this->set($key, $value); +	} + +	/** +	 * Get the value for specified key +	 * +	 * @see ArrayAccess::offsetGet() +	 * +	 * @param int|string $key The key to retrieve. +	 * @return mixed +	 */ +	public function offsetGet($key) { +		return $this->get($key); +	} + +	/** +	 * Unsets a key. +	 * +	 * @see ArrayAccess::offsetUnset() +	 * +	 * @param int|string $key The key to unset. +	 * @return void +	 */ +	public function offsetUnset($key) { +		$this->remove($key); +	} + +	/** +	 * Does key exist? +	 * +	 * @see ArrayAccess::offsetExists() +	 * +	 * @param int|string $key A key to check for. +	 * @return boolean +	 */ +	public function offsetExists($key) { +		return $this->containsKey($key); +	} +} diff --git a/engine/classes/ElggMemcache.php b/engine/classes/ElggMemcache.php index d9539b9cb..91d50ab89 100644 --- a/engine/classes/ElggMemcache.php +++ b/engine/classes/ElggMemcache.php @@ -32,6 +32,8 @@ class ElggMemcache extends ElggSharedMemoryCache {  	 *  	 * @param string $namespace The namespace for this cache to write to -  	 * note, namespaces of the same name are shared! +	 * +	 * @throws ConfigurationException  	 */  	function __construct($namespace = 'default') {  		global $CONFIG; diff --git a/engine/classes/ElggMenuBuilder.php b/engine/classes/ElggMenuBuilder.php index d9a704dd9..b463143d8 100644 --- a/engine/classes/ElggMenuBuilder.php +++ b/engine/classes/ElggMenuBuilder.php @@ -8,6 +8,9 @@   */  class ElggMenuBuilder { +	/** +	 * @var ElggMenuItem[] +	 */  	protected $menu = array();  	protected $selected = null; @@ -15,7 +18,7 @@ class ElggMenuBuilder {  	/**  	 * ElggMenuBuilder constructor  	 * -	 * @param array $menu Array of ElggMenuItem objects +	 * @param ElggMenuItem[] $menu Array of ElggMenuItem objects  	 */  	public function __construct(array $menu) {  		$this->menu = $menu; @@ -107,6 +110,7 @@ class ElggMenuBuilder {  			$children = array();  			// divide base nodes from children  			foreach ($section as $menu_item) { +				/* @var ElggMenuItem $menu_item */  				$parent_name = $menu_item->getParentName();  				if (!$parent_name) {  					$parents[$menu_item->getName()] = $menu_item; @@ -118,13 +122,16 @@ class ElggMenuBuilder {  			// attach children to parents  			$iteration = 0;  			$current_gen = $parents; +			$next_gen = null;  			while (count($children) && $iteration < 5) {  				foreach ($children as $index => $menu_item) {  					$parent_name = $menu_item->getParentName();  					if (array_key_exists($parent_name, $current_gen)) {  						$next_gen[$menu_item->getName()] = $menu_item; -						$current_gen[$parent_name]->addChild($menu_item); -						$menu_item->setParent($current_gen[$parent_name]); +						if (!in_array($menu_item, $current_gen[$parent_name]->getData('children'))) { +							$current_gen[$parent_name]->addChild($menu_item); +							$menu_item->setParent($current_gen[$parent_name]); +						}  						unset($children[$index]);  					}  				} @@ -216,12 +223,12 @@ class ElggMenuBuilder {  				array_push($stack, $root);  				while (!empty($stack)) {  					$node = array_pop($stack); +					/* @var ElggMenuItem $node */  					$node->sortChildren($sort_callback);  					$children = $node->getChildren();  					if ($children) {  						$stack = array_merge($stack, $children);  					} -					$p = count($stack);  				}  			}  		} @@ -230,8 +237,8 @@ class ElggMenuBuilder {  	/**  	 * Compare two menu items by their display text  	 * -	 * @param ElggMenuItem $a -	 * @param ElggMenuItem $b +	 * @param ElggMenuItem $a Menu item +	 * @param ElggMenuItem $b Menu item  	 * @return bool  	 */  	public static function compareByText($a, $b) { @@ -248,8 +255,8 @@ class ElggMenuBuilder {  	/**  	 * Compare two menu items by their identifiers  	 * -	 * @param ElggMenuItem $a -	 * @param ElggMenuItem $b +	 * @param ElggMenuItem $a Menu item +	 * @param ElggMenuItem $b Menu item  	 * @return bool  	 */  	public static function compareByName($a, $b) { @@ -266,9 +273,11 @@ class ElggMenuBuilder {  	/**  	 * Compare two menu items by their priority  	 * -	 * @param ElggMenuItem $a -	 * @param ElggMenuItem $b +	 * @param ElggMenuItem $a Menu item +	 * @param ElggMenuItem $b Menu item  	 * @return bool +	 * +	 * @todo change name to compareByPriority  	 */  	public static function compareByWeight($a, $b) {  		$aw = $a->getWeight(); diff --git a/engine/classes/ElggMetadata.php b/engine/classes/ElggMetadata.php index 7f45dc3ea..3a8e2d817 100644 --- a/engine/classes/ElggMetadata.php +++ b/engine/classes/ElggMetadata.php @@ -6,6 +6,10 @@   *   * @package    Elgg.Core   * @subpackage Metadata + * + * @property string $value_type + * @property int $owner_guid + * @property string $enabled   */  class ElggMetadata extends ElggExtender { diff --git a/engine/classes/ElggObject.php b/engine/classes/ElggObject.php index 6263f84f6..aeaa3ba5c 100644 --- a/engine/classes/ElggObject.php +++ b/engine/classes/ElggObject.php @@ -66,21 +66,18 @@ class ElggObject extends ElggEntity {  					$msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid));  					throw new IOException($msg);  				} - -			// Is $guid is an ElggObject? Use a copy constructor  			} else if ($guid instanceof ElggObject) { +				// $guid is an ElggObject so this is a copy constructor  				elgg_deprecated_notice('This type of usage of the ElggObject constructor was deprecated. Please use the clone method.', 1.7);  				foreach ($guid->attributes as $key => $value) {  					$this->attributes[$key] = $value;  				} - -			// Is this is an ElggEntity but not an ElggObject = ERROR!  			} else if ($guid instanceof ElggEntity) { +				// @todo remove - do not need separate exception  				throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggObject')); - -			// Is it a GUID  			} else if (is_numeric($guid)) { +				// $guid is a GUID so load  				if (!$this->load($guid)) {  					throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid)));  				} @@ -110,7 +107,7 @@ class ElggObject extends ElggEntity {  		$this->attributes = $attrs;  		$this->attributes['tables_loaded'] = 2; -		cache_entity($this); +		_elgg_cache_entity($this);  		return true;  	} @@ -129,8 +126,12 @@ class ElggObject extends ElggEntity {  		}  		// Save ElggObject-specific attributes -		return create_object_entity($this->get('guid'), $this->get('title'), -			$this->get('description')); + +		_elgg_disable_caching_for_entity($this->guid); +		$ret = create_object_entity($this->get('guid'), $this->get('title'), $this->get('description')); +		_elgg_enable_caching_for_entity($this->guid); + +		return $ret;  	}  	/** diff --git a/engine/classes/ElggPlugin.php b/engine/classes/ElggPlugin.php index 8f71b79a8..545b9a53c 100644 --- a/engine/classes/ElggPlugin.php +++ b/engine/classes/ElggPlugin.php @@ -145,7 +145,7 @@ class ElggPlugin extends ElggObject {  	/**  	 * Sets the location of this plugin.  	 * -	 * @param path $id The path to the plugin's dir. +	 * @param string $id The path to the plugin's dir.  	 * @return bool  	 */  	public function setID($id) { @@ -299,17 +299,15 @@ class ElggPlugin extends ElggObject {  		$private_settings = get_data($q); -		if ($private_settings) { -			$return = array(); +		$return = array(); +		if ($private_settings) {  			foreach ($private_settings as $setting) {  				$return[$setting->name] = $setting->value;  			} - -			return $return;  		} -		return false; +		return $return;  	}  	/** @@ -350,11 +348,14 @@ class ElggPlugin extends ElggObject {  	 */  	public function unsetAllSettings() {  		$db_prefix = get_config('dbprefix'); -		$ps_prefix = elgg_namespace_plugin_private_setting('setting', ''); + +		$us_prefix = elgg_namespace_plugin_private_setting('user_setting', '', $this->getID()); +		$is_prefix = elgg_namespace_plugin_private_setting('internal', '', $this->getID());  		$q = "DELETE FROM {$db_prefix}private_settings  			WHERE entity_guid = $this->guid -			AND name NOT LIKE '$ps_prefix%'"; +			AND name NOT LIKE '$us_prefix%' +			AND name NOT LIKE '$is_prefix%'";  		return delete_data($q);  	} @@ -420,20 +421,18 @@ class ElggPlugin extends ElggObject {  		$private_settings = get_data($q); -		if ($private_settings) { -			$return = array(); +		$return = array(); +		if ($private_settings) {  			foreach ($private_settings as $setting) {  				$name = substr($setting->name, $ps_prefix_len);  				$value = $setting->value;  				$return[$name] = $value;  			} - -			return $return;  		} -		return false; +		return $return;  	}  	/** @@ -546,7 +545,7 @@ class ElggPlugin extends ElggObject {  	 * Returns if the plugin is complete, meaning has all required files  	 * and Elgg can read them and they make sense.  	 * -	 * @todo bad name? This could be confused with isValid() from ElggPackage. +	 * @todo bad name? This could be confused with isValid() from ElggPluginPackage.  	 *  	 * @return bool  	 */ @@ -597,6 +596,8 @@ class ElggPlugin extends ElggObject {  	 * Checks if this plugin can be activated on the current  	 * Elgg installation.  	 * +	 * @todo remove $site_guid param or implement it +	 *  	 * @param mixed $site_guid Optional site guid  	 * @return bool  	 */ @@ -647,8 +648,8 @@ class ElggPlugin extends ElggObject {  			// Note: this will not run re-run the init hooks!  			if ($return) {  				if ($this->canReadFile('activate.php')) { -					$flags = ELGG_PLUGIN_INCLUDE_START | ELGG_PLUGIN_REGISTER_CLASSES -							| ELGG_PLUGIN_REGISTER_LANGUAGES | ELGG_PLUGIN_REGISTER_VIEWS; +					$flags = ELGG_PLUGIN_INCLUDE_START | ELGG_PLUGIN_REGISTER_CLASSES | +							ELGG_PLUGIN_REGISTER_LANGUAGES | ELGG_PLUGIN_REGISTER_VIEWS;  					$this->start($flags); diff --git a/engine/classes/ElggPluginPackage.php b/engine/classes/ElggPluginPackage.php index 2dc4bdb3d..37eb4bf4d 100644 --- a/engine/classes/ElggPluginPackage.php +++ b/engine/classes/ElggPluginPackage.php @@ -100,7 +100,6 @@ class ElggPluginPackage {  	 * @param string $plugin   The ID (directory name) or full path of the plugin.  	 * @param bool   $validate Automatically run isValid()?  	 * -	 * @return true  	 * @throws PluginException  	 */  	public function __construct($plugin, $validate = true) { @@ -213,6 +212,7 @@ class ElggPluginPackage {  			return false;  		} +		// Note: $conflicts and $requires are not unused. They're called dynamically  		$conflicts = $this->getManifest()->getConflicts();  		$requires = $this->getManifest()->getRequires();  		$provides = $this->getManifest()->getProvides(); @@ -294,6 +294,7 @@ class ElggPluginPackage {  			return true;  		} +		$this->errorMsg = elgg_echo('unknown_error');  		return false;  	} @@ -330,8 +331,10 @@ class ElggPluginPackage {  	 * @return bool|array  	 */  	public function checkDependencies($full_report = false) { +		// Note: $conflicts and $requires are not unused. They're called dynamically  		$requires = $this->getManifest()->getRequires();  		$conflicts = $this->getManifest()->getConflicts(); +  		$enabled_plugins = elgg_get_plugins('active');  		$this_id = $this->getID();  		$report = array(); @@ -368,6 +371,7 @@ class ElggPluginPackage {  		$check_types = array('requires', 'conflicts');  		if ($full_report) { +			// Note: $suggests is not unused. It's called dynamically  			$suggests = $this->getManifest()->getSuggests();  			$check_types[] = 'suggests';  		} diff --git a/engine/classes/ElggPriorityList.php b/engine/classes/ElggPriorityList.php index 8a3b836a8..416df885c 100644 --- a/engine/classes/ElggPriorityList.php +++ b/engine/classes/ElggPriorityList.php @@ -89,7 +89,7 @@   *	return true;   * }   * - * @package Elgg.Core + * @package    Elgg.Core   * @subpackage Helpers   */  class ElggPriorityList @@ -126,7 +126,9 @@ class ElggPriorityList  	 *                        maintains its priority and the new element is to the next available  	 *                        slot, taking into consideration all previously registered elements.  	 *                        Negative elements are accepted. +	 * @param bool  $exact    unused  	 * @return int            The priority of the added element. +	 * @todo remove $exact or implement it. Note we use variable name strict below.  	 */  	public function add($element, $priority = null, $exact = false) {  		if ($priority !== null && !is_numeric($priority)) { @@ -146,7 +148,8 @@ class ElggPriorityList  	 * @warning The element must have the same attributes / values. If using $strict, it must have  	 *          the same types. array(10) will fail in strict against array('10') (str vs int).  	 * -	 * @param type $element +	 * @param mixed $element The element to remove from the list +	 * @param bool  $strict  Whether to check the type of the element match  	 * @return bool  	 */  	public function remove($element, $strict = false) { @@ -162,10 +165,10 @@ class ElggPriorityList  	/**  	 * Move an existing element to a new priority.  	 * -	 * @param mixed  $current_priority -	 * @param int    $new_priority -	 * -	 * @return int The new priority. +	 * @param mixed $element      The element to move +	 * @param int   $new_priority The new priority for the element +	 * @param bool  $strict       Whether to check the type of the element match +	 * @return bool  	 */  	public function move($element, $new_priority, $strict = false) {  		$new_priority = (int) $new_priority; @@ -200,12 +203,12 @@ class ElggPriorityList  	 *  	 * If no user function is provided the elements are sorted by priority registered.  	 * -	 * The callback function should accept the array of elements as the first argument and should -	 * return a sorted array. +	 * The callback function should accept the array of elements as the first  +	 * argument and should return a sorted array.  	 *  	 * This function can be called multiple times.  	 * -	 * @param type $callback +	 * @param callback $callback The callback for sorting. Numeric sorting is the default.  	 * @return bool  	 */  	public function sort($callback = null) { @@ -268,7 +271,7 @@ class ElggPriorityList  	/**  	 * Returns the element at $priority.  	 * -	 * @param int $priority +	 * @param int $priority The priority  	 * @return mixed The element or false on fail.  	 */  	public function getElement($priority) { @@ -351,7 +354,12 @@ class ElggPriorityList  		return ($key !== NULL && $key !== FALSE);  	} -	// Countable +	/** +	 * Countable interface +	 * +	 * @see Countable::count() +	 * @return int +	 */  	public function count() {  		return count($this->elements);  	} diff --git a/engine/classes/ElggRelationship.php b/engine/classes/ElggRelationship.php index efc0f7eff..d2e88882a 100644 --- a/engine/classes/ElggRelationship.php +++ b/engine/classes/ElggRelationship.php @@ -71,6 +71,7 @@ class ElggRelationship extends ElggData implements  	 * Save the relationship  	 *  	 * @return int the relationship id +	 * @throws IOException  	 */  	public function save() {  		if ($this->id > 0) { @@ -145,7 +146,7 @@ class ElggRelationship extends ElggData implements  	 * @param ODD $data ODD data  	 * @return bool -	 * @throws ImportException +	 * @throws ImportException|InvalidParameterException  	 */  	public function import(ODD $data) {  		if (!($data instanceof ODDRelationship)) { @@ -179,6 +180,8 @@ class ElggRelationship extends ElggData implements  				return true;  			}  		} + +		return false;  	}  	// SYSTEM LOG INTERFACE //////////////////////////////////////////////////////////// diff --git a/engine/classes/ElggSite.php b/engine/classes/ElggSite.php index 1fe49b85c..dd996fe98 100644 --- a/engine/classes/ElggSite.php +++ b/engine/classes/ElggSite.php @@ -77,28 +77,24 @@ class ElggSite extends ElggEntity {  					$msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid));  					throw new IOException($msg);  				} - -			// Is $guid is an ElggSite? Use a copy constructor  			} else if ($guid instanceof ElggSite) { +				// $guid is an ElggSite so this is a copy constructor  				elgg_deprecated_notice('This type of usage of the ElggSite constructor was deprecated. Please use the clone method.', 1.7);  				foreach ($guid->attributes as $key => $value) {  					$this->attributes[$key] = $value;  				} - -			// Is this is an ElggEntity but not an ElggSite = ERROR!  			} else if ($guid instanceof ElggEntity) { +				// @todo remove and just use else clause  				throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggSite')); - -			// See if this is a URL  			} else if (strpos($guid, "http") !== false) { +				// url so retrieve by url  				$guid = get_site_by_url($guid);  				foreach ($guid->attributes as $key => $value) {  					$this->attributes[$key] = $value;  				} - -			// Is it a GUID  			} else if (is_numeric($guid)) { +				// $guid is a GUID so load  				if (!$this->load($guid)) {  					throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid)));  				} @@ -128,7 +124,7 @@ class ElggSite extends ElggEntity {  		$this->attributes = $attrs;  		$this->attributes['tables_loaded'] = 2; -		cache_entity($this); +		_elgg_cache_entity($this);  		return true;  	} @@ -381,7 +377,9 @@ class ElggSite extends ElggEntity {  				elgg_register_plugin_hook_handler('index', 'system', 'elgg_walled_garden_index', 1);  				if (!$this->isPublicPage()) { -					$_SESSION['last_forward_from'] = current_page_url(); +					if (!elgg_is_xhr()) { +						$_SESSION['last_forward_from'] = current_page_url(); +					}  					register_error(elgg_echo('loggedinrequired'));  					forward();  				} @@ -443,8 +441,6 @@ class ElggSite extends ElggEntity {  		// include a hook for plugin authors to include public pages  		$plugins = elgg_trigger_plugin_hook('public_pages', 'walled_garden', NULL, array()); -		// lookup admin-specific public pages -  		// allow public pages  		foreach (array_merge($defaults, $plugins) as $public) {  			$pattern = "`^{$CONFIG->url}$public/*$`i"; diff --git a/engine/classes/ElggStaticVariableCache.php b/engine/classes/ElggStaticVariableCache.php index 17d849400..9c14fdfba 100644 --- a/engine/classes/ElggStaticVariableCache.php +++ b/engine/classes/ElggStaticVariableCache.php @@ -11,7 +11,7 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache {  	/**  	 * The cache.  	 * -	 * @var unknown_type +	 * @var array  	 */  	private static $__cache; @@ -22,7 +22,7 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache {  	 * memory, optionally with a given namespace (to avoid overlap).  	 *  	 * @param string $namespace The namespace for this cache to write to. -	 * @note namespaces of the same name are shared! +	 * @warning namespaces of the same name are shared!  	 */  	function __construct($namespace = 'default') {  		$this->setNamespace($namespace); @@ -80,7 +80,7 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache {  	}  	/** -	 * This was probably meant to delete everything? +	 * Clears the cache for a particular namespace  	 *  	 * @return void  	 */ diff --git a/engine/classes/ElggTranslit.php b/engine/classes/ElggTranslit.php index 676c59fc8..b4bf87797 100644 --- a/engine/classes/ElggTranslit.php +++ b/engine/classes/ElggTranslit.php @@ -20,11 +20,10 @@   * 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 + * @package Elgg.Core + * @author  Konsta Vesterinen <kvesteri@cc.hut.fi> + * @author  Jonathan H. Wage <jonwage@gmail.com> + * @author  Steve Clay <steve@mrclay.org>   *   * @access private Plugin authors should not use this directly   */ @@ -32,8 +31,9 @@ class ElggTranslit {  	/**  	 * Create a version of a string for embedding in a URL -	 * @param string $string a UTF-8 string -	 * @param string $separator +	 * +	 * @param string $string    A UTF-8 string +	 * @param string $separator The character to separate words with  	 * @return string  	 */  	static public function urlize($string, $separator = '-') { @@ -49,24 +49,29 @@ class ElggTranslit {  		// Internationalization, AND 日本語!  		$string = self::transliterateAscii($string); -		// more translation +		// allow HTML tags in titles +		$string = preg_replace('~<([a-zA-Z][^>]*)>~', ' $1 ', $string); + +		// more substitutions +		// @todo put these somewhere else  		$string = strtr($string, array( -			// Euro/GBP -			"\xE2\x82\xAC" /* € */ => 'E', "\xC2\xA3" /* £ */ => 'GBP', +			// currency +			"\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 +			. '\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, '', ''); @@ -80,10 +85,10 @@ class ElggTranslit {  		// 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 +			. '\x00-\x2f'  // controls ... slash +			. '\x3a-\x40'  // : ... @ +			. '\x5b-\x60'  // [ ... ` +			. '\x7b-\x7f'  // { ... DEL  			. ']+~x';  		// ['internationalization', 'and', '日本語'] @@ -98,6 +103,7 @@ class ElggTranslit {  	/**  	 * Transliterate Western multibyte chars to ASCII +	 *  	 * @param string $utf8 a UTF-8 string  	 * @return string  	 */ @@ -247,6 +253,7 @@ class ElggTranslit {  	/**  	 * Tests that "normalizer_normalize" exists and works +	 *  	 * @return bool  	 */  	static public function hasNormalizerSupport() { @@ -255,7 +262,7 @@ class ElggTranslit {  			$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)); +				&& $form_c === normalizer_normalize($form_d));  		}  		return $ret;  	} diff --git a/engine/classes/ElggUser.php b/engine/classes/ElggUser.php index 6c1cdc1de..6163f9b62 100644 --- a/engine/classes/ElggUser.php +++ b/engine/classes/ElggUser.php @@ -40,6 +40,9 @@ class ElggUser extends ElggEntity  		$this->attributes['code'] = NULL;  		$this->attributes['banned'] = "no";  		$this->attributes['admin'] = 'no'; +		$this->attributes['prev_last_action'] = NULL; +		$this->attributes['last_login'] = NULL; +		$this->attributes['prev_last_login'] = NULL;  		$this->attributes['tables_split'] = 2;  	} @@ -65,30 +68,26 @@ class ElggUser extends ElggEntity  					$msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid));  					throw new IOException($msg);  				} - -			// See if this is a username  			} else if (is_string($guid)) { +				// $guid is a username  				$user = get_user_by_username($guid);  				if ($user) {  					foreach ($user->attributes as $key => $value) {  						$this->attributes[$key] = $value;  					}  				} - -			// Is $guid is an ElggUser? Use a copy constructor  			} else if ($guid instanceof ElggUser) { +				// $guid is an ElggUser so this is a copy constructor  				elgg_deprecated_notice('This type of usage of the ElggUser constructor was deprecated. Please use the clone method.', 1.7);  				foreach ($guid->attributes as $key => $value) {  					$this->attributes[$key] = $value;  				} - -			// Is this is an ElggEntity but not an ElggUser = ERROR!  			} else if ($guid instanceof ElggEntity) { +				// @todo why have a special case here  				throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggUser')); - -			// Is it a GUID  			} else if (is_numeric($guid)) { +				// $guid is a GUID so load entity  				if (!$this->load($guid)) {  					throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid)));  				} @@ -116,7 +115,7 @@ class ElggUser extends ElggEntity  		$this->attributes = $attrs;  		$this->attributes['tables_loaded'] = 2; -		cache_entity($this); +		_elgg_cache_entity($this);  		return true;  	} @@ -133,9 +132,13 @@ class ElggUser extends ElggEntity  		}  		// Now save specific stuff -		return create_user_entity($this->get('guid'), $this->get('name'), $this->get('username'), +		_elgg_disable_caching_for_entity($this->guid); +		$ret = create_user_entity($this->get('guid'), $this->get('name'), $this->get('username'),  			$this->get('password'), $this->get('salt'), $this->get('email'), $this->get('language'),  			$this->get('code')); +		_elgg_enable_caching_for_entity($this->guid); + +		return $ret;  	}  	/** diff --git a/engine/classes/ElggVolatileMetadataCache.php b/engine/classes/ElggVolatileMetadataCache.php index 8a33c198d..4acda7cee 100644 --- a/engine/classes/ElggVolatileMetadataCache.php +++ b/engine/classes/ElggVolatileMetadataCache.php @@ -33,9 +33,11 @@ class ElggVolatileMetadataCache {  	protected $ignoreAccess = null;  	/** -	 * @param int $entity_guid -	 * -	 * @param array $values +	 * Cache metadata for an entity +	 *  +	 * @param int   $entity_guid The GUID of the entity +	 * @param array $values      The metadata values to cache +	 * @return void  	 */  	public function saveAll($entity_guid, array $values) {  		if (!$this->getIgnoreAccess()) { @@ -45,8 +47,9 @@ class ElggVolatileMetadataCache {  	}  	/** -	 * @param int $entity_guid -	 * +	 * Get the metadata for an entity +	 *  +	 * @param int $entity_guid The GUID of the entity  	 * @return array  	 */  	public function loadAll($entity_guid) { @@ -61,15 +64,17 @@ class ElggVolatileMetadataCache {  	 * Declare that there may be fetch-able metadata names in storage that this  	 * cache doesn't know about  	 * -	 * @param int $entity_guid +	 * @param int $entity_guid The GUID of the entity +	 * @return void  	 */  	public function markOutOfSync($entity_guid) {  		unset($this->isSynchronized[$entity_guid]);  	}  	/** -	 * @param $entity_guid -	 * +	 * Have all the metadata for this entity been cached? +	 *  +	 * @param int $entity_guid The GUID of the entity  	 * @return bool  	 */  	public function isSynchronized($entity_guid) { @@ -77,13 +82,15 @@ class ElggVolatileMetadataCache {  	}  	/** -	 * @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 +	 * Cache a piece of metadata +	 *  +	 * @param int                   $entity_guid    The GUID of the entity +	 * @param string                $name           The metadata name +	 * @param array|int|string|null $value          The metadata value. null means it is  +	 *                                              known that there is no fetch-able  +	 *                                              metadata under this name +	 * @param bool                  $allow_multiple Can the metadata be an array +	 * @return void  	 */  	public function save($entity_guid, $name, $value, $allow_multiple = false) {  		if ($this->getIgnoreAccess()) { @@ -115,10 +122,8 @@ class ElggVolatileMetadataCache {  	 * function's return value should be trusted (otherwise a null return value  	 * is ambiguous).  	 * -	 * @param int $entity_guid -	 * -	 * @param string $name -	 * +	 * @param int    $entity_guid The GUID of the entity +	 * @param string $name        The metadata name  	 * @return array|string|int|null null = value does not exist  	 */  	public function load($entity_guid, $name) { @@ -133,9 +138,9 @@ class ElggVolatileMetadataCache {  	 * 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 +	 * @param int    $entity_guid The GUID of the entity +	 * @param string $name        The metadata name +	 * @return void  	 */  	public function markUnknown($entity_guid, $name) {  		unset($this->values[$entity_guid][$name]); @@ -145,10 +150,8 @@ class ElggVolatileMetadataCache {  	/**  	 * If true, load() will return an accurate value for this name  	 * -	 * @param int $entity_guid -	 * -	 * @param string $name -	 * +	 * @param int    $entity_guid The GUID of the entity +	 * @param string $name        The metadata name  	 * @return bool  	 */  	public function isKnown($entity_guid, $name) { @@ -163,10 +166,8 @@ class ElggVolatileMetadataCache {  	/**  	 * Declare that metadata under this name is known to be not fetch-able from storage  	 * -	 * @param int $entity_guid -	 * -	 * @param string $name -	 * +	 * @param int    $entity_guid The GUID of the entity +	 * @param string $name        The metadata name  	 * @return array  	 */  	public function markEmpty($entity_guid, $name) { @@ -176,7 +177,8 @@ class ElggVolatileMetadataCache {  	/**  	 * Forget about all metadata for an entity  	 * -	 * @param int $entity_guid +	 * @param int $entity_guid The GUID of the entity +	 * @return void  	 */  	public function clear($entity_guid) {  		$this->values[$entity_guid] = array(); @@ -185,6 +187,8 @@ class ElggVolatileMetadataCache {  	/**  	 * Clear entire cache and mark all entities as out of sync +	 *  +	 * @return void  	 */  	public function flush() {  		$this->values = array(); @@ -197,7 +201,8 @@ class ElggVolatileMetadataCache {  	 *  	 * This setting makes this component a little more loosely-coupled.  	 * -	 * @param bool $ignore +	 * @param bool $ignore Whether to ignore access or not +	 * @return void  	 */  	public function setIgnoreAccess($ignore) {  		$this->ignoreAccess = (bool) $ignore; @@ -205,12 +210,16 @@ class ElggVolatileMetadataCache {  	/**  	 * Tell the cache to call elgg_get_ignore_access() to determing access status. +	 *  +	 * @return void  	 */  	public function unsetIgnoreAccess() {  		$this->ignoreAccess = null;  	}  	/** +	 * Get the ignore access value +	 *   	 * @return bool  	 */  	protected function getIgnoreAccess() { @@ -225,12 +234,10 @@ class ElggVolatileMetadataCache {  	 * 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 +	 * @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 +	 * @return void  	 */  	public function invalidateByOptions($action, array $options) {  		// remove as little as possible, optimizing for common cases @@ -254,7 +261,10 @@ class ElggVolatileMetadataCache {  	}  	/** -	 * @param int|array $guids +	 * Populate the cache from a set of entities +	 *  +	 * @param int|array $guids Array of or single GUIDs +	 * @return void  	 */  	public function populateFromEntities($guids) {  		if (empty($guids)) { @@ -318,9 +328,7 @@ class ElggVolatileMetadataCache {  	 * 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) -	 * +	 * @param int   $limit Limit in characters of all metadata (with ints casted to strings)  	 * @return array  	 */  	public function filterMetadataHeavyEntities(array $guids, $limit = 1024000) { diff --git a/engine/classes/ElggWidget.php b/engine/classes/ElggWidget.php index 99708f66a..66191bf47 100644 --- a/engine/classes/ElggWidget.php +++ b/engine/classes/ElggWidget.php @@ -7,6 +7,11 @@   *   * @package    Elgg.Core   * @subpackage Widgets + * + * @property-read string $handler internal, do not use + * @property-read string $column internal, do not use + * @property-read string $order internal, do not use + * @property-read string $context internal, do not use   */  class ElggWidget extends ElggObject { @@ -141,10 +146,15 @@ class ElggWidget extends ElggObject {  			}  		} +		$bottom_rank = count($widgets); +		if ($column == $this->column) { +			$bottom_rank--; +		} +		  		if ($rank == 0) {  			// top of the column  			$this->order = reset($widgets)->order - 10; -		} elseif ($rank == (count($widgets) - 1)) { +		} elseif ($rank == $bottom_rank) {  			// bottom of the column of active widgets  			$this->order = end($widgets)->order + 10;  		} else { diff --git a/engine/classes/ElggXMLElement.php b/engine/classes/ElggXMLElement.php index 65a13912c..cbd3fc5ce 100644 --- a/engine/classes/ElggXMLElement.php +++ b/engine/classes/ElggXMLElement.php @@ -20,7 +20,12 @@ class ElggXMLElement {  		if ($xml instanceof SimpleXMLElement) {  			$this->_element = $xml;  		} else { +			// do not load entities +			$disable_load_entities = libxml_disable_entity_loader(true); +  			$this->_element = new SimpleXMLElement($xml); + +			libxml_disable_entity_loader($disable_load_entities);  		}  	} @@ -32,7 +37,7 @@ class ElggXMLElement {  	}  	/** -	 * @return array:string The attributes +	 * @return string[] The attributes  	 */  	public function getAttributes() {  		//include namespace declarations as attributes @@ -64,7 +69,7 @@ class ElggXMLElement {  	}  	/** -	 * @return array:ElggXMLElement Child elements +	 * @return ElggXMLElement[] Child elements  	 */  	public function getChildren() {  		$children = $this->_element->children(); @@ -76,6 +81,12 @@ class ElggXMLElement {  		return $result;  	} +	/** +	 * Override -> +	 *  +	 * @param string $name Property name +	 * @return mixed +	 */  	function __get($name) {  		switch ($name) {  			case 'name': @@ -94,6 +105,12 @@ class ElggXMLElement {  		return null;  	} +	/** +	 * Override isset +	 *  +	 * @param string $name Property name +	 * @return boolean +	 */  	function __isset($name) {  		switch ($name) {  			case 'name': @@ -111,5 +128,4 @@ class ElggXMLElement {  		}  		return false;  	} - -}
\ No newline at end of file +} diff --git a/engine/classes/ODDMetaData.php b/engine/classes/ODDMetaData.php index 58862e0fb..09b653582 100644 --- a/engine/classes/ODDMetaData.php +++ b/engine/classes/ODDMetaData.php @@ -10,12 +10,12 @@ class ODDMetaData extends ODD {  	/**  	 * New ODD metadata  	 * -	 * @param unknown_type $uuid        Unique ID -	 * @param unknown_type $entity_uuid Another unique ID -	 * @param unknown_type $name        Name -	 * @param unknown_type $value       Value -	 * @param unknown_type $type        Type -	 * @param unknown_type $owner_uuid  Owner ID +	 * @param string $uuid        Unique ID +	 * @param string $entity_uuid Another unique ID +	 * @param string $name        Name +	 * @param string $value       Value +	 * @param string $type        Type +	 * @param string $owner_uuid  Owner ID  	 */  	function __construct($uuid, $entity_uuid, $name, $value, $type = "", $owner_uuid = "") {  		parent::__construct(); @@ -31,7 +31,7 @@ class ODDMetaData extends ODD {  	/**  	 * Returns 'metadata'  	 * -	 * @return 'metadata' +	 * @return string 'metadata'  	 */  	protected function getTagName() {  		return "metadata"; diff --git a/engine/classes/ODDRelationship.php b/engine/classes/ODDRelationship.php index 2906b1c73..8b1fe217b 100644 --- a/engine/classes/ODDRelationship.php +++ b/engine/classes/ODDRelationship.php @@ -10,9 +10,9 @@ class ODDRelationship extends ODD {  	/**  	 * New ODD Relationship  	 * -	 * @param unknown_type $uuid1 First UUID -	 * @param unknown_type $type  Type of telationship -	 * @param unknown_type $uuid2 Second UUId +	 * @param string $uuid1 First UUID +	 * @param string $type  Type of telationship +	 * @param string $uuid2 Second UUId  	 */  	function __construct($uuid1, $type, $uuid2) {  		parent::__construct(); @@ -25,7 +25,7 @@ class ODDRelationship extends ODD {  	/**  	 * Returns 'relationship'  	 * -	 * @return 'relationship' +	 * @return string 'relationship'  	 */  	protected function getTagName() {  		return "relationship"; diff --git a/engine/handlers/cache_handler.php b/engine/handlers/cache_handler.php index 7706c2c92..36fc665bb 100644 --- a/engine/handlers/cache_handler.php +++ b/engine/handlers/cache_handler.php @@ -88,15 +88,18 @@ header("ETag: \"$etag\"");  $filename = $dataroot . 'views_simplecache/' . md5($viewtype . $view);  if (file_exists($filename)) { -	$contents = file_get_contents($filename); +	readfile($filename);  } else {  	// someone trying to access a non-cached file or a race condition with cache flushing  	mysql_close($mysql_dblink);  	require_once(dirname(dirname(__FILE__)) . "/start.php"); -	elgg_regenerate_simplecache(); + +	global $CONFIG; +	if (!in_array($view, $CONFIG->views->simplecache)) { +		header("HTTP/1.1 404 Not Found"); +		exit; +	}  	elgg_set_viewtype($viewtype); -	$contents = elgg_view($view); +	echo elgg_view($view);  } - -echo $contents; diff --git a/engine/lib/access.php b/engine/lib/access.php index f7d3bf7ea..de0693ea8 100644 --- a/engine/lib/access.php +++ b/engine/lib/access.php @@ -1015,6 +1015,10 @@ function access_init() {   *   * Returns true to override the access system or null if no change is needed.   * + * @param string $hook + * @param string $type + * @param bool $value + * @param array $params   * @return true|null   * @access private   */ @@ -1047,6 +1051,13 @@ function elgg_override_permissions($hook, $type, $value, $params) {  /**   * Runs unit tests for the entities object. + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array + *   * @access private   */  function access_test($hook, $type, $value, $params) { diff --git a/engine/lib/actions.php b/engine/lib/actions.php index 53b185dea..8047914ac 100644 --- a/engine/lib/actions.php +++ b/engine/lib/actions.php @@ -65,18 +65,16 @@ function action($action, $forwarder = "") {  	// @todo REMOVE THESE ONCE #1509 IS IN PLACE.  	// Allow users to disable plugins without a token in order to  	// remove plugins that are incompatible. -	// Login and logout are for convenience. +	// Logout for convenience.  	// file/download (see #2010)  	$exceptions = array(  		'admin/plugins/disable',  		'logout', -		'login',  		'file/download',  	);  	if (!in_array($action, $exceptions)) { -		// All actions require a token. -		action_gatekeeper(); +		action_gatekeeper($action);  	}  	$forwarder = str_replace(elgg_get_site_url(), "", $forwarder); @@ -189,6 +187,26 @@ function elgg_unregister_action($action) {  }  /** + * Is the token timestamp within acceptable range? + *  + * @param int $ts timestamp from the CSRF token + *  + * @return bool + */ +function _elgg_validate_token_timestamp($ts) { +	$action_token_timeout = elgg_get_config('action_token_timeout'); +	// default is 2 hours +	$timeout = ($action_token_timeout !== null) ? $action_token_timeout : 2; + +	$hour = 60 * 60; +	$timeout = $timeout * $hour; +	$now = time(); + +	// Validate time to ensure its not crazy +	return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout)); +} + +/**   * Validate an action token.   *   * Calls to actions will automatically validate tokens. If tokens are not @@ -206,8 +224,6 @@ function elgg_unregister_action($action) {   * @access private   */  function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) { -	global $CONFIG; -  	if (!$token) {  		$token = get_input('__elgg_token');  	} @@ -216,29 +232,18 @@ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL)  		$ts = get_input('__elgg_ts');  	} -	if (!isset($CONFIG->action_token_timeout)) { -		// default to 2 hours -		$timeout = 2; -	} else { -		$timeout = $CONFIG->action_token_timeout; -	} -  	$session_id = session_id();  	if (($token) && ($ts) && ($session_id)) {  		// generate token, check with input and forward if invalid -		$generated_token = generate_action_token($ts); +		$required_token = generate_action_token($ts);  		// Validate token -		if ($token == $generated_token) { -			$hour = 60 * 60; -			$timeout = $timeout * $hour; -			$now = time(); - -			// Validate time to ensure its not crazy -			if ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout)) { +		if ($token == $required_token) { +			 +			if (_elgg_validate_token_timestamp($ts)) {  				// We have already got this far, so unless anything -				// else says something to the contry we assume we're ok +				// else says something to the contrary we assume we're ok  				$returnval = true;  				$returnval = elgg_trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', array( @@ -252,10 +257,20 @@ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL)  					register_error(elgg_echo('actiongatekeeper:pluginprevents'));  				}  			} else if ($visibleerrors) { -				register_error(elgg_echo('actiongatekeeper:timeerror')); +				// this is necessary because of #5133 +				if (elgg_is_xhr()) { +					register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); +				} else { +					register_error(elgg_echo('actiongatekeeper:timeerror')); +				}  			}  		} else if ($visibleerrors) { -			register_error(elgg_echo('actiongatekeeper:tokeninvalid')); +			// this is necessary because of #5133 +			if (elgg_is_xhr()) { +				register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); +			} else { +				register_error(elgg_echo('actiongatekeeper:tokeninvalid')); +			}  		}  	} else {  		if (! empty($_SERVER['CONTENT_LENGTH']) && empty($_POST)) { @@ -284,12 +299,33 @@ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL)   * This function verifies form input for security features (like a generated token),   * and forwards if they are invalid.   * + * @param string $action The action being performed + *    * @return mixed True if valid or redirects.   * @access private   */ -function action_gatekeeper() { -	if (validate_action_token()) { -		return TRUE; +function action_gatekeeper($action) { +	if ($action === 'login') { +		if (validate_action_token(false)) { +			return true; +		} +		 +		$token = get_input('__elgg_token'); +		$ts = (int)get_input('__elgg_ts'); +		if ($token && _elgg_validate_token_timestamp($ts)) { +			// The tokens are present and the time looks valid: this is probably a mismatch due to the  +			// login form being on a different domain. +			register_error(elgg_echo('actiongatekeeper:crosssitelogin')); +			 +			 +			forward('login', 'csrf'); +		} +		 +		// let the validator send an appropriate msg +		validate_action_token(); +		 +	} elseif (validate_action_token()) { +		return true;  	}  	forward(REFERER, 'csrf'); @@ -328,16 +364,19 @@ function generate_action_token($timestamp) {  }  /** - * Initialise the site secret hash. + * Initialise the site secret (32 bytes: "z" to indicate format + 186-bit key in Base64 URL).   *   * Used during installation and saves as a datalist.   * + * Note: Old secrets were hex encoded. + *   * @return mixed The site secret hash or false   * @access private   * @todo Move to better file.   */  function init_site_secret() { -	$secret = md5(rand() . microtime()); +	$secret = 'z' . ElggCrypto::getRandomString(31); +  	if (datalist_set('__site_secret__', $secret)) {  		return $secret;  	} @@ -364,6 +403,26 @@ function get_site_secret() {  }  /** + * Get the strength of the site secret + * + * @return string "strong", "moderate", or "weak" + * @access private + */ +function _elgg_get_site_secret_strength() { +	$secret = get_site_secret(); +	if ($secret[0] !== 'z') { +		$rand_max = getrandmax(); +		if ($rand_max < pow(2, 16)) { +			return 'weak'; +		} +		if ($rand_max < pow(2, 32)) { +			return 'moderate'; +		} +	} +	return 'strong'; +} + +/**   * Check if an action is registered and its script exists.   *   * @param string $action Action name diff --git a/engine/lib/admin.php b/engine/lib/admin.php index 35ab5599d..f36f29668 100644 --- a/engine/lib/admin.php +++ b/engine/lib/admin.php @@ -134,11 +134,11 @@ function elgg_delete_admin_notice($id) {  }  /** - * List all admin messages. + * Get admin notices. An admin must be logged in since the notices are private.   *   * @param int $limit Limit   * - * @return array List of admin notices + * @return array Array of admin notices   * @since 1.8.0   */  function elgg_get_admin_notices($limit = 10) { @@ -158,11 +158,13 @@ function elgg_get_admin_notices($limit = 10) {   * @since 1.8.0   */  function elgg_admin_notice_exists($id) { +	$old_ia = elgg_set_ignore_access(true);  	$notice = elgg_get_entities_from_metadata(array(  		'type' => 'object',  		'subtype' => 'admin_notice',  		'metadata_name_value_pair' => array('name' => 'admin_notice_id', 'value' => $id)  	)); +	elgg_set_ignore_access($old_ia);  	return ($notice) ? TRUE : FALSE;  } @@ -234,6 +236,7 @@ function admin_init() {  	elgg_register_action('admin/site/update_advanced', '', 'admin');  	elgg_register_action('admin/site/flush_cache', '', 'admin');  	elgg_register_action('admin/site/unlock_upgrade', '', 'admin'); +	elgg_register_action('admin/site/regenerate_secret', '', 'admin');  	elgg_register_action('admin/menu/save', '', 'admin'); @@ -289,6 +292,7 @@ function admin_init() {  	elgg_register_admin_menu_item('configure', 'settings', null, 100);  	elgg_register_admin_menu_item('configure', 'basic', 'settings', 10);  	elgg_register_admin_menu_item('configure', 'advanced', 'settings', 20); +	elgg_register_admin_menu_item('configure', 'advanced/site_secret', 'settings', 25);  	elgg_register_admin_menu_item('configure', 'menu_items', 'appearance', 30);  	elgg_register_admin_menu_item('configure', 'profile_fields', 'appearance', 40);  	// default widgets is added via an event handler elgg_default_widgets_init() in widgets.php @@ -346,7 +350,7 @@ function elgg_admin_add_plugin_settings_menu() {  	$active_plugins = elgg_get_plugins('active');  	if (!$active_plugins) {  		// nothing added because no items -		return FALSE; +		return;  	}  	foreach ($active_plugins as $plugin) { @@ -380,6 +384,7 @@ function elgg_admin_add_plugin_settings_menu() {   */  function elgg_admin_sort_page_menu($hook, $type, $return, $params) {  	$configure_items = $return['configure']; +	/* @var ElggMenuItem[] $configure_items */  	foreach ($configure_items as $menu_item) {  		if ($menu_item->getName() == 'settings') {  			$settings = $menu_item; @@ -387,6 +392,7 @@ function elgg_admin_sort_page_menu($hook, $type, $return, $params) {  	}  	// keep the basic and advanced settings at the top +	/* @var ElggMenuItem $settings */  	$children = $settings->getChildren();  	$site_settings = array_splice($children, 0, 2);  	usort($children, array('ElggMenuBuilder', 'compareByText')); @@ -466,14 +472,18 @@ function admin_page_handler($page) {  	$vars = array('page' => $page);  	// special page for plugin settings since we create the form for them -	if ($page[0] == 'plugin_settings' && isset($page[1]) && -		(elgg_view_exists("settings/{$page[1]}/edit") || elgg_view_exists("plugins/{$page[1]}/settings"))) { +	if ($page[0] == 'plugin_settings') { +		if (isset($page[1]) && (elgg_view_exists("settings/{$page[1]}/edit") ||  +			elgg_view_exists("plugins/{$page[1]}/settings"))) { -		$view = 'admin/plugin_settings'; -		$plugin = elgg_get_plugin_from_id($page[1]); -		$vars['plugin'] = $plugin; +			$view = 'admin/plugin_settings'; +			$plugin = elgg_get_plugin_from_id($page[1]); +			$vars['plugin'] = $plugin; -		$title = elgg_echo("admin:{$page[0]}"); +			$title = elgg_echo("admin:{$page[0]}"); +		} else { +			forward('', '404'); +		}  	} else {  		$view = 'admin/' . implode('/', $page);  		$title = elgg_echo("admin:{$page[0]}"); @@ -552,7 +562,7 @@ function admin_plugin_screenshot_page_handler($pages) {   *	* COPYRIGHT.txt   *	* LICENSE.txt   * - * @param type $page + * @param array $pages   * @return bool   * @access private   */ @@ -615,7 +625,11 @@ function admin_markdown_page_handler($pages) {  /**   * Adds default admin widgets to the admin dashboard.   * - * @return void + * @param string $event + * @param string $type + * @param ElggUser $user + * + * @return null|true   * @access private   */  function elgg_add_admin_widgets($event, $type, $user) { @@ -637,6 +651,7 @@ function elgg_add_admin_widgets($event, $type, $user) {  			$guid = elgg_create_widget($user->getGUID(), $handler, 'admin');  			if ($guid) {  				$widget = get_entity($guid); +				/* @var ElggWidget $widget */  				$widget->move($column, $position);  			}  		} diff --git a/engine/lib/annotations.php b/engine/lib/annotations.php index 3b9f84703..5e9b530de 100644 --- a/engine/lib/annotations.php +++ b/engine/lib/annotations.php @@ -17,6 +17,7 @@   */  function row_to_elggannotation($row) {  	if (!($row instanceof stdClass)) { +		// @todo should throw in this case?  		return $row;  	} @@ -30,7 +31,7 @@ function row_to_elggannotation($row) {   *   * @param int $id The id of the annotation object being retrieved.   * - * @return false|ElggAnnotation + * @return ElggAnnotation|false   */  function elgg_get_annotation_from_id($id) {  	return elgg_get_metastring_based_object_from_id($id, 'annotations'); @@ -195,10 +196,22 @@ function update_annotation($annotation_id, $name, $value, $value_type, $owner_gu   *                                   for the proper use of the "calculation" option.   *   * - * @return mixed + * @return ElggAnnotation[]|mixed   * @since 1.8.0   */  function elgg_get_annotations(array $options = array()) { + +	// @todo remove support for count shortcut - see #4393 +	if (isset($options['__egefac']) && $options['__egefac']) { +		unset($options['__egefac']); +	} else { +		// support shortcut of 'count' => true for 'annotation_calculation' => 'count' +		if (isset($options['count']) && $options['count']) { +			$options['annotation_calculation'] = 'count'; +			unset($options['count']); +		}		 +	} +	  	$options['metastring_type'] = 'annotations';  	return elgg_get_metastring_based_objects($options);  } @@ -211,7 +224,7 @@ function elgg_get_annotations(array $options = array()) {   *          annotation_name(s), annotation_value(s), or guid(s) must be set.   *   * @param array $options An options array. {@See elgg_get_annotations()} - * @return mixed Null if the metadata name is invalid. Bool on success or fail. + * @return bool|null true on success, false on failure, null if no annotations to delete.   * @since 1.8.0   */  function elgg_delete_annotations(array $options) { @@ -229,16 +242,20 @@ function elgg_delete_annotations(array $options) {   * @warning Unlike elgg_get_annotations() this will not accept an empty options array!   *   * @param array $options An options array. {@See elgg_get_annotations()} - * @return mixed + * @return bool|null true on success, false on failure, null if no annotations disabled.   * @since 1.8.0   */  function elgg_disable_annotations(array $options) {  	if (!elgg_is_valid_options_for_batch_operation($options, 'annotations')) {  		return false;  	} +	 +	// if we can see hidden (disabled) we need to use the offset +	// otherwise we risk an infinite loop if there are more than 50 +	$inc_offset = access_get_show_hidden_status();  	$options['metastring_type'] = 'annotations'; -	return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', false); +	return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset);  }  /** @@ -246,8 +263,11 @@ function elgg_disable_annotations(array $options) {   *   * @warning Unlike elgg_get_annotations() this will not accept an empty options array!   * + * @warning In order to enable annotations, you must first use + * {@link access_show_hidden_entities()}. + *   * @param array $options An options array. {@See elgg_get_annotations()} - * @return mixed + * @return bool|null true on success, false on failure, null if no metadata enabled.   * @since 1.8.0   */  function elgg_enable_annotations(array $options) { @@ -403,8 +423,8 @@ function elgg_list_entities_from_annotations($options = array()) {  function elgg_get_entities_from_annotation_calculation($options) {  	$db_prefix = elgg_get_config('dbprefix');  	$defaults = array( -		'calculation'	=>	'sum', -		'order_by'		=>	'annotation_calculation desc' +		'calculation' => 'sum', +		'order_by' => 'annotation_calculation desc'  	);  	$options = array_merge($defaults, $options); @@ -424,6 +444,10 @@ function elgg_get_entities_from_annotation_calculation($options) {  	$options['callback'] = 'entity_row_to_elggstar'; +	// see #4393 +	// @todo remove after the 'count' shortcut is removed from elgg_get_annotations() +	$options['__egefac'] = true; +  	return elgg_get_annotations($options);  } @@ -437,23 +461,30 @@ function elgg_get_entities_from_annotation_calculation($options) {   * @return string   */  function elgg_list_entities_from_annotation_calculation($options) { +	$defaults = array( +		'calculation' => 'sum', +		'order_by' => 'annotation_calculation desc' +	); +	$options = array_merge($defaults, $options); +  	return elgg_list_entities($options, 'elgg_get_entities_from_annotation_calculation');  }  /** - * Handler called by trigger_plugin_hook on the "export" event. + * Export the annotations for the specified entity   *   * @param string $hook        'export' - * @param string $entity_type 'all' + * @param string $type        'all'   * @param mixed  $returnvalue Default return value - * @param mixed  $params      List of params to export + * @param mixed  $params      Parameters determining what annotations to export   *   * @elgg_plugin_hook export all   * - * @return mixed + * @return array + * @throws InvalidParameterException   * @access private   */ -function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $params) { +function export_annotation_plugin_hook($hook, $type, $returnvalue, $params) {  	// Sanity check values  	if ((!is_array($params)) && (!isset($params['guid']))) {  		throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); @@ -464,12 +495,12 @@ function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $param  	}  	$guid = (int)$params['guid']; -	$name = $params['name']; +	$options = array('guid' => $guid, 'limit' => 0); +	if (isset($params['name'])) { +		$options['annotation_name'] = $params['name']; +	} -	$result = elgg_get_annotations(array( -		'guid' => $guid, -		'limit' => 0 -	)); +	$result = elgg_get_annotations($options);  	if ($result) {  		foreach ($result as $r) { @@ -514,15 +545,16 @@ function elgg_annotation_exists($entity_guid, $annotation_type, $owner_guid = NU  		return FALSE;  	} -	$entity_guid = (int)$entity_guid; -	$annotation_type = sanitise_string($annotation_type); +	$entity_guid = sanitize_int($entity_guid); +	$owner_guid = sanitize_int($owner_guid); +	$annotation_type = sanitize_string($annotation_type); -	$sql = "select a.id" . -			" FROM {$CONFIG->dbprefix}annotations a, {$CONFIG->dbprefix}metastrings m " . -			" WHERE a.owner_guid={$owner_guid} AND a.entity_guid={$entity_guid} " . -			" AND a.name_id=m.id AND m.string='{$annotation_type}'"; +	$sql = "SELECT a.id FROM {$CONFIG->dbprefix}annotations a" . +			" JOIN {$CONFIG->dbprefix}metastrings m ON a.name_id = m.id" . +			" WHERE a.owner_guid = $owner_guid AND a.entity_guid = $entity_guid" . +			" AND m.string = '$annotation_type'"; -	if ($check_annotation = get_data_row($sql)) { +	if (get_data_row($sql)) {  		return TRUE;  	} @@ -541,6 +573,7 @@ function elgg_comment_url_handler(ElggAnnotation $comment) {  	if ($entity) {  		return $entity->getURL() . '#item-annotation-' . $comment->id;  	} +	return "";  }  /** @@ -557,6 +590,12 @@ function elgg_register_annotation_url_handler($extender_name = "all", $function_  /**   * Register annotation unit tests + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array   * @access private   */  function annotations_test($hook, $type, $value, $params) { diff --git a/engine/lib/cache.php b/engine/lib/cache.php index be1c43e14..3116c1a9b 100644 --- a/engine/lib/cache.php +++ b/engine/lib/cache.php @@ -125,7 +125,7 @@ function elgg_get_filepath_cache() {   * @access private   */  function elgg_filepath_cache_reset() { -	return elgg_reset_system_cache(); +	elgg_reset_system_cache();  }  /**   * @access private @@ -143,13 +143,13 @@ function elgg_filepath_cache_load($type) {   * @access private   */  function elgg_enable_filepath_cache() { -	return elgg_enable_system_cache(); +	elgg_enable_system_cache();  }  /**   * @access private   */  function elgg_disable_filepath_cache() { -	return elgg_disable_system_cache(); +	elgg_disable_system_cache();  }  /* Simplecache */ @@ -208,6 +208,7 @@ function elgg_get_simplecache_url($type, $view) {  	global $CONFIG;  	$lastcache = (int)$CONFIG->lastcache;  	$viewtype = elgg_get_viewtype(); +	elgg_register_simplecache_view("$type/$view");// see #5302  	if (elgg_is_simplecache_enabled()) {  		$url = elgg_get_site_url() . "cache/$type/$viewtype/$view.$lastcache.$type";  	} else { @@ -222,7 +223,7 @@ function elgg_get_simplecache_url($type, $view) {  /**   * Regenerates the simple cache.   * - * @warning This does not invalidate the cache, but actively resets it. + * @warning This does not invalidate the cache, but actively rebuilds it.   *   * @param string $viewtype Optional viewtype to regenerate. Defaults to all valid viewtypes.   * @@ -444,7 +445,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)); +			elgg_save_system_cache("$lang.lang", serialize($map));  		}  	}  } diff --git a/engine/lib/calendar.php b/engine/lib/calendar.php index 9a06c5292..e6f95934c 100644 --- a/engine/lib/calendar.php +++ b/engine/lib/calendar.php @@ -39,6 +39,8 @@ function get_day_end($day = null, $month = null, $year = null) {  /**   * Return the notable entities for a given time period.   * + * @todo this function also accepts an array(type => subtypes) for 3rd arg. Should we document this? + *   * @param int     $start_time     The start time as a unix timestamp.   * @param int     $end_time       The end time as a unix timestamp.   * @param string  $type           The type of entity (eg "user", "object" etc) diff --git a/engine/lib/configuration.php b/engine/lib/configuration.php index b10e51130..55e5bbd36 100644 --- a/engine/lib/configuration.php +++ b/engine/lib/configuration.php @@ -36,6 +36,7 @@ function elgg_get_site_url($site_guid = 0) {  	if (!$site instanceof ElggSite) {  		return false;  	} +	/* @var ElggSite $site */  	return $site->url;  } @@ -138,7 +139,7 @@ function elgg_set_config($name, $value) {  /**   * Save a configuration setting   * - * @param string $name      Configuration name (cannot be greater than 32 characters) + * @param string $name      Configuration name (cannot be greater than 255 characters)   * @param mixed  $value     Configuration value. Should be string for installation setting   * @param int    $site_guid NULL for installation setting, 0 for default site   * @@ -173,7 +174,7 @@ function elgg_save_config($name, $value, $site_guid = 0) {  /**   * Check that installation has completed and the database is populated.   * - * @throws InstallationException + * @throws InstallationException|DatabaseException   * @return void   * @access private   */ @@ -181,7 +182,7 @@ function verify_installation() {  	global $CONFIG;  	if (isset($CONFIG->installed)) { -		return $CONFIG->installed; +		return;  	}  	try { @@ -227,9 +228,9 @@ function datalist_get($name) {  	$name = trim($name); -	// cannot store anything longer than 32 characters in db, so catch here -	if (elgg_strlen($name) > 32) { -		elgg_log("The name length for configuration variables cannot be greater than 32", "ERROR"); +	// cannot store anything longer than 255 characters in db, so catch here +	if (elgg_strlen($name) > 255) { +		elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR");  		return false;  	} @@ -286,7 +287,7 @@ function datalist_get($name) {  function datalist_set($name, $value) {  	global $CONFIG, $DATALIST_CACHE; -	// cannot store anything longer than 32 characters in db, so catch before we set +	// cannot store anything longer than 255 characters in db, so catch before we set  	if (elgg_strlen($name) > 255) {  		elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR");  		return false; @@ -332,7 +333,7 @@ function datalist_set($name, $value) {   * This will cause the run once function to be run on all installations.  To perform   * additional upgrades, create new functions for each release.   * - * @warning The function name cannot be longer than 32 characters long due to + * @warning The function name cannot be longer than 255 characters long due to   * the current schema for the datalist table.   *   * @internal A datalist entry $functioname is created with the value of time(). @@ -407,7 +408,7 @@ function unset_config($name, $site_guid = 0) {   * @param string $value     Its value   * @param int    $site_guid Optionally, the GUID of the site (current site is assumed by default)   * - * @return 0 + * @return bool   * @todo The config table doens't have numeric primary keys so insert_data returns 0.   * @todo Use "INSERT ... ON DUPLICATE KEY UPDATE" instead of trying to delete then add.   * @see unset_config() @@ -419,9 +420,9 @@ function set_config($name, $value, $site_guid = 0) {  	$name = trim($name); -	// cannot store anything longer than 32 characters in db, so catch before we set -	if (elgg_strlen($name) > 32) { -		elgg_log("The name length for configuration variables cannot be greater than 32", "ERROR"); +	// cannot store anything longer than 255 characters in db, so catch before we set +	if (elgg_strlen($name) > 255) { +		elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR");  		return false;  	} @@ -485,9 +486,9 @@ function get_config($name, $site_guid = 0) {  	// @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"; +		//	$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/cron.php b/engine/lib/cron.php index f7a032f4a..4f3d05b93 100644 --- a/engine/lib/cron.php +++ b/engine/lib/cron.php @@ -26,11 +26,10 @@ function cron_init() {   * @param array $page Pages   *   * @return bool + * @throws CronException   * @access private   */  function cron_page_handler($page) { -	global $CONFIG; -  	if (!isset($page[0])) {  		forward();  	} @@ -51,7 +50,6 @@ function cron_page_handler($page) {  	$params['time'] = time();  	// Data to return to -	$std_out = "";  	$old_stdout = "";  	ob_start(); diff --git a/engine/lib/database.php b/engine/lib/database.php index 7d90b30b8..a7949788d 100644 --- a/engine/lib/database.php +++ b/engine/lib/database.php @@ -12,15 +12,19 @@  /**   * Query cache for all queries.   * - * Each query and its results are stored in this array as: + * Each query and its results are stored in this cache as:   * <code> - * $DB_QUERY_CACHE[$query] => array(result1, result2, ... resultN) + * $DB_QUERY_CACHE[query hash] => array(result1, result2, ... resultN)   * </code> + * @see elgg_query_runner() for details on the hash.   * - * @global array $DB_QUERY_CACHE + * @warning Elgg used to set this as an empty array to turn off the cache + * + * @global ElggLRUCache|null $DB_QUERY_CACHE + * @access private   */  global $DB_QUERY_CACHE; -$DB_QUERY_CACHE = array(); +$DB_QUERY_CACHE = null;  /**   * Queries to be executed upon shutdown. @@ -38,6 +42,7 @@ $DB_QUERY_CACHE = array();   * </code>   *   * @global array $DB_DELAYED_QUERIES + * @access private   */  global $DB_DELAYED_QUERIES;  $DB_DELAYED_QUERIES = array(); @@ -48,7 +53,8 @@ $DB_DELAYED_QUERIES = array();   * Each database link created with establish_db_link($name) is stored in   * $dblink as $dblink[$name] => resource.  Use get_db_link($name) to retrieve it.   * - * @global array $dblink + * @global resource[] $dblink + * @access private   */  global $dblink;  $dblink = array(); @@ -59,6 +65,7 @@ $dblink = array();   * Each call to the database increments this counter.   *   * @global integer $dbcalls + * @access private   */  global $dbcalls;  $dbcalls = 0; @@ -72,11 +79,12 @@ $dbcalls = 0;   * resource. eg "read", "write", or "readwrite".   *   * @return void + * @throws DatabaseException   * @access private   */  function establish_db_link($dblinkname = "readwrite") {  	// Get configuration, and globalise database link -	global $CONFIG, $dblink, $DB_QUERY_CACHE, $dbcalls; +	global $CONFIG, $dblink, $DB_QUERY_CACHE;  	if ($dblinkname != "readwrite" && isset($CONFIG->db[$dblinkname])) {  		if (is_array($CONFIG->db[$dblinkname])) { @@ -120,7 +128,8 @@ function establish_db_link($dblinkname = "readwrite") {  	// Set up cache if global not initialized and query cache not turned off  	if ((!$DB_QUERY_CACHE) && (!$db_cache_off)) { -		$DB_QUERY_CACHE = new ElggStaticVariableCache('db_query_cache'); +		// @todo if we keep this cache in 1.9, expose the size as a config parameter +		$DB_QUERY_CACHE = new ElggLRUCache(200);  	}  } @@ -134,7 +143,7 @@ function establish_db_link($dblinkname = "readwrite") {   * @access private   */  function setup_db_connections() { -	global $CONFIG, $dblink; +	global $CONFIG;  	if (!empty($CONFIG->db->split)) {  		establish_db_link('read'); @@ -197,7 +206,7 @@ function db_delayedexecution_shutdown_hook() {   *   * @param string $dblinktype The type of link we want: "read", "write" or "readwrite".   * - * @return object Database link + * @return resource Database link   * @access private   */  function get_db_link($dblinktype) { @@ -216,7 +225,7 @@ function get_db_link($dblinktype) {  /**   * Execute an EXPLAIN for $query.   * - * @param str   $query The query to explain + * @param string $query The query to explain   * @param mixed $link  The database link resource to user.   *   * @return mixed An object of the query's result, or FALSE @@ -240,14 +249,14 @@ function explain_query($query, $link) {   * {@link $dbcalls} is incremented and the query is saved into the {@link $DB_QUERY_CACHE}.   *   * @param string $query  The query - * @param link   $dblink The DB link + * @param resource   $dblink The DB link   * - * @return The result of mysql_query() + * @return resource result of mysql_query()   * @throws DatabaseException   * @access private   */  function execute_query($query, $dblink) { -	global $CONFIG, $dbcalls; +	global $dbcalls;  	if ($query == NULL) {  		throw new DatabaseException(elgg_echo('DatabaseException:InvalidQuery')); @@ -275,7 +284,7 @@ function execute_query($query, $dblink) {   * the raw result from {@link mysql_query()}.   *   * @param string   $query   The query to execute - * @param resource $dblink  The database link to use or the link type (read | write) + * @param resource|string $dblink  The database link to use or the link type (read | write)   * @param string   $handler A callback function to pass the results array to   *   * @return true @@ -386,20 +395,18 @@ function get_data_row($query, $callback = "") {   * @access private   */  function elgg_query_runner($query, $callback = null, $single = false) { -	global $CONFIG, $DB_QUERY_CACHE; +	global $DB_QUERY_CACHE;  	// Since we want to cache results of running the callback, we need to  	// need to namespace the query with the callback and single result request. -	// http://trac.elgg.org/ticket/4049 +	// https://github.com/elgg/elgg/issues/4049  	$hash = (string)$callback . (int)$single . $query;  	// Is cached?  	if ($DB_QUERY_CACHE) { -		$cached_query = $DB_QUERY_CACHE[$hash]; - -		if ($cached_query !== FALSE) { +		if (isset($DB_QUERY_CACHE[$hash])) {  			elgg_log("DB query $query results returned from cache (hash: $hash)", 'NOTICE'); -			return $cached_query; +			return $DB_QUERY_CACHE[$hash];  		}  	} @@ -410,7 +417,7 @@ function elgg_query_runner($query, $callback = null, $single = false) {  		// test for callback once instead of on each iteration.  		// @todo check profiling to see if this needs to be broken out into -		// explicit cases instead of checking in the interation. +		// explicit cases instead of checking in the iteration.  		$is_callable = is_callable($callback);  		while ($row = mysql_fetch_object($result)) {  			if ($is_callable) { @@ -451,18 +458,12 @@ function elgg_query_runner($query, $callback = null, $single = false) {   * @access private   */  function insert_data($query) { -	global $CONFIG, $DB_QUERY_CACHE;  	elgg_log("DB query $query", 'NOTICE');  	$dblink = get_db_link('write'); -	// Invalidate query cache -	if ($DB_QUERY_CACHE) { -		$DB_QUERY_CACHE->clear(); -	} - -	elgg_log("Query cache invalidated", 'NOTICE'); +	_elgg_invalidate_query_cache();  	if (execute_query("$query", $dblink)) {  		return mysql_insert_id($dblink); @@ -472,7 +473,7 @@ function insert_data($query) {  }  /** - * Update a row in the database. + * Update the database.   *   * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}.   * @@ -482,17 +483,12 @@ function insert_data($query) {   * @access private   */  function update_data($query) { -	global $CONFIG, $DB_QUERY_CACHE;  	elgg_log("DB query $query", 'NOTICE');  	$dblink = get_db_link('write'); -	// Invalidate query cache -	if ($DB_QUERY_CACHE) { -		$DB_QUERY_CACHE->clear(); -		elgg_log("Query cache invalidated", 'NOTICE'); -	} +	_elgg_invalidate_query_cache();  	if (execute_query("$query", $dblink)) {  		return TRUE; @@ -502,7 +498,7 @@ function update_data($query) {  }  /** - * Remove a row from the database. + * Remove data from the database.   *   * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}.   * @@ -512,17 +508,12 @@ function update_data($query) {   * @access private   */  function delete_data($query) { -	global $CONFIG, $DB_QUERY_CACHE;  	elgg_log("DB query $query", 'NOTICE');  	$dblink = get_db_link('write'); -	// Invalidate query cache -	if ($DB_QUERY_CACHE) { -		$DB_QUERY_CACHE->clear(); -		elgg_log("Query cache invalidated", 'NOTICE'); -	} +	_elgg_invalidate_query_cache();  	if (execute_query("$query", $dblink)) {  		return mysql_affected_rows($dblink); @@ -531,6 +522,22 @@ function delete_data($query) {  	return FALSE;  } +/** + * Invalidate the query cache + * + * @access private + */ +function _elgg_invalidate_query_cache() { +	global $DB_QUERY_CACHE; +	if ($DB_QUERY_CACHE instanceof ElggLRUCache) { +		$DB_QUERY_CACHE->clear(); +		elgg_log("Query cache invalidated", 'NOTICE'); +	} elseif ($DB_QUERY_CACHE) { +		// In case someone sets the cache to an array and primes it with data +		$DB_QUERY_CACHE = array(); +		elgg_log("Query cache invalidated", 'NOTICE'); +	} +}  /**   * Return tables matching the database prefix {@link $CONFIG->dbprefix}% in the currently @@ -638,7 +645,7 @@ function run_sql_script($scriptlocation) {  			$statement = str_replace("prefix_", $CONFIG->dbprefix, $statement);  			if (!empty($statement)) {  				try { -					$result = update_data($statement); +					update_data($statement);  				} catch (DatabaseException $e) {  					$errors[] = $e->getMessage();  				} @@ -661,7 +668,7 @@ function run_sql_script($scriptlocation) {  /**   * Format a query string for logging - *  + *   * @param string $query Query string   * @return string   * @access private diff --git a/engine/lib/deprecated-1.7.php b/engine/lib/deprecated-1.7.php index 519eea89d..ee95b5611 100644 --- a/engine/lib/deprecated-1.7.php +++ b/engine/lib/deprecated-1.7.php @@ -1137,6 +1137,7 @@ function make_register_object($register_name, $register_value, $children_array =   * @param int $guid GUID   *   * @return 1 + * @deprecated 1.7   */  function delete_object_entity($guid) {  	system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); @@ -1154,6 +1155,7 @@ function delete_object_entity($guid) {   * @param int $guid User GUID   *   * @return 1 + * @deprecated 1.7   */  function delete_user_entity($guid) {  	system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); diff --git a/engine/lib/deprecated-1.8.php b/engine/lib/deprecated-1.8.php index 4b9d41543..91068d047 100644 --- a/engine/lib/deprecated-1.8.php +++ b/engine/lib/deprecated-1.8.php @@ -87,7 +87,7 @@ function list_entities_from_access_id($access_id, $entity_type = "", $entity_sub  	elgg_deprecated_notice("All list_entities* functions were deprecated in 1.8.  Use elgg_list_entities* instead.", 1.8);  	echo elgg_list_entities_from_access_id(array('access_id' => $access_id, -		'types' => $entity_type, 'subtypes' => $entity_subtype, 'owner_guids' => $owner_guid, +		'type' => $entity_type, 'subtype' => $entity_subtype, 'owner_guids' => $owner_guid,  		'limit' => $limit, 'full_view' => $fullview, 'list_type_toggle' => $listtypetoggle,  		'pagination' => $pagination,));  } @@ -1314,8 +1314,8 @@ function list_entities_from_metadata($meta_name, $meta_value = "", $entity_type  	$options = array(  		'metadata_name' => $meta_name,  		'metadata_value' => $meta_value, -		'types' => $entity_type, -		'subtypes' => $entity_subtype, +		'type' => $entity_type, +		'subtype' => $entity_subtype,  		'limit' => $limit,  		'offset' => $offset,  		'count' => TRUE, @@ -2120,8 +2120,8 @@ $fullview = true, $listtypetoggle = false, $pagination = true, $order_by = '') {  		'relationship' => $relationship,  		'relationship_guid' => $relationship_guid,  		'inverse_relationship' => $inverse_relationship, -		'types' => $type, -		'subtypes' => $subtype, +		'type' => $type, +		'subtype' => $subtype,  		'owner_guid' => $owner_guid,  		'order_by' => $order_by,  		'limit' => $limit, @@ -2566,9 +2566,9 @@ $owner_guid = "", $owner_relationship = "") {  				'relationship' => $owner_relationship,  				'relationship_guid' => $owner_guid[0],  				'inverse_relationship' => FALSE, -				'types' => 'user', -				'subtypes' => $subtype, -				'limit' => 9999)) +				'type' => 'user', +				'subtype' => $subtype, +				'limit' => false))  			) {  				$friendsarray = array(); @@ -2721,8 +2721,8 @@ function get_site_collections($site_guid, $subtype = "", $limit = 10, $offset =  		'relationship' => 'member_of_site',  		'relationship_guid' => $site_guid,  		'inverse_relationship' => TRUE, -		'types' => 'collection', -		'subtypes' => $subtype, +		'type' => 'collection', +		'subtype' => $subtype,  		'limit' => $limit,  		'offset' => $offset  	)); @@ -3414,6 +3414,7 @@ function list_annotations($entity_guid, $name = "", $limit = 25, $asc = true) {   * @param unknown_type $timeupper   * @param unknown_type $calculation   * @internal Don't use this at all. + * @deprecated 1.8 Use elgg_get_annotations()   */  function elgg_deprecated_annotation_calculation($entity_guid = 0, $entity_type = "", $entity_subtype = "",  $name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, @@ -4667,6 +4668,7 @@ function display_widget(ElggObject $widget) {   *   * @param ElggEntity $entity   * @return int Number of comments + * @deprecated 1.8 Use ElggEntity->countComments()   */  function elgg_count_comments($entity) {  	elgg_deprecated_notice('elgg_count_comments() is deprecated by ElggEntity->countComments()', 1.8); @@ -4772,3 +4774,47 @@ function default_page_handler($page, $handler) {  	return FALSE;  } + +/** + * Invalidate this class's entry in the cache. + * + * @param int $guid The entity guid + * + * @return void + * @access private + * @deprecated 1.8 + */ +function invalidate_cache_for_entity($guid) { +	elgg_deprecated_notice('invalidate_cache_for_entity() is a private function and should not be used.', 1.8); +	_elgg_invalidate_cache_for_entity($guid); +} + +/** + * Cache an entity. + * + * Stores an entity in $ENTITY_CACHE; + * + * @param ElggEntity $entity Entity to cache + * + * @return void + * @access private + * @deprecated 1.8 + */ +function cache_entity(ElggEntity $entity) { +	elgg_deprecated_notice('cache_entity() is a private function and should not be used.', 1.8); +	_elgg_cache_entity($entity); +} + +/** + * Retrieve a entity from the cache. + * + * @param int $guid The guid + * + * @return ElggEntity|bool false if entity not cached, or not fully loaded + * @access private + * @deprecated 1.8 + */ +function retrieve_cached_entity($guid) { +	elgg_deprecated_notice('retrieve_cached_entity() is a private function and should not be used.', 1.8); +	return _elgg_retrieve_cached_entity($guid); +} diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index 540605876..34111c69d 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -93,10 +93,17 @@ function elgg_register_library($name, $location) {   * @return void   * @throws InvalidParameterException   * @since 1.8.0 + * @todo return boolean in 1.9 to indicate whether the library has been loaded   */  function elgg_load_library($name) {  	global $CONFIG; +	static $loaded_libraries = array(); + +	if (in_array($name, $loaded_libraries)) { +		return; +	} +  	if (!isset($CONFIG->libraries)) {  		$CONFIG->libraries = array();  	} @@ -113,6 +120,8 @@ function elgg_load_library($name) {  		);  		throw new InvalidParameterException($error);  	} + +	$loaded_libraries[] = $name;  }  /** @@ -124,12 +133,11 @@ function elgg_load_library($name) {   * @param string $location URL to forward to browser to. Can be path relative to the network's URL.   * @param string $reason   Short explanation for why we're forwarding   * - * @return False False if headers have been sent. Terminates execution if forwarding. + * @return false False if headers have been sent. Terminates execution if forwarding. + * @throws SecurityException   */  function forward($location = "", $reason = 'system') { -	global $CONFIG; - -	if (!headers_sent()) { +	if (!headers_sent($file, $line)) {  		if ($location === REFERER) {  			$location = $_SERVER['HTTP_REFERER'];  		} @@ -148,7 +156,7 @@ function forward($location = "", $reason = 'system') {  			exit;  		}  	} else { -		throw new SecurityException(elgg_echo('SecurityException:ForwardFailedToRedirect')); +		throw new SecurityException(elgg_echo('SecurityException:ForwardFailedToRedirect', array($file, $line)));  	}  } @@ -384,7 +392,7 @@ function elgg_load_external_file($type, $name) {  		$item->url = '';  		$item->location = ''; -		$priority = $CONFIG->externals[$type]->add($item); +		$CONFIG->externals[$type]->add($item);  		$CONFIG->externals_map[$type][$name] = $item;  	}  } @@ -528,7 +536,7 @@ function sanitise_filepath($path, $append_slash = TRUE) {   * @param string $register Types of message: "error", "success" (default: success)   * @param bool   $count    Count the number of messages (default: false)   * - * @return true|false|array Either the array of messages, or a response regarding + * @return bool|array Either the array of messages, or a response regarding   *                          whether the message addition was successful.   * @todo Clean up. Separate registering messages and retrieving them.   */ @@ -562,7 +570,7 @@ function system_messages($message = null, $register = "success", $count = false)  			return sizeof($_SESSION['msg'][$register]);  		} else {  			$count = 0; -			foreach ($_SESSION['msg'] as $register => $submessages) { +			foreach ($_SESSION['msg'] as $submessages) {  				$count += sizeof($submessages);  			}  			return $count; @@ -738,7 +746,7 @@ function elgg_unregister_event_handler($event, $object_type, $callback) {   * @tip When referring to events, the preferred syntax is "event, type".   *   * @internal Only rarely should events be changed, added, or removed in core. - * When making changes to events, be sure to first create a ticket in trac. + * When making changes to events, be sure to first create a ticket on Github.   *   * @internal @tip Think of $object_type as the primary namespace element, and   * $event as the secondary namespace. @@ -839,7 +847,7 @@ function elgg_trigger_event($event, $object_type, $object = null) {   *   * @param string   $hook     The name of the hook   * @param string   $type     The type of the hook - * @param callback $callback The name of a valid function or an array with object and method + * @param callable $callback The name of a valid function or an array with object and method   * @param int      $priority The priority - 500 is default, lower numbers called first   *   * @return bool @@ -885,7 +893,7 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority =   *   * @param string   $hook        The name of the hook   * @param string   $entity_type The name of the type of entity (eg "user", "object" etc) - * @param callback $callback    The PHP callback to be removed + * @param callable $callback    The PHP callback to be removed   *   * @return void   * @since 1.8.0 @@ -1060,6 +1068,7 @@ function _elgg_php_exception_handler($exception) {   * @param array  $vars     An array that points to the active symbol table where error occurred   *   * @return true + * @throws Exception   * @access private   * @todo Replace error_log calls with elgg_log calls.   */ @@ -1185,6 +1194,11 @@ function elgg_dump($value, $to_screen = TRUE, $level = 'NOTICE') {  		$to_screen = FALSE;  	} +	// Do not want to write to JS or CSS pages +	if (elgg_in_context('js') || elgg_in_context('css')) { +		$to_screen = FALSE; +	} +  	if ($to_screen == TRUE) {  		echo '<pre>';  		print_r($value); @@ -1292,8 +1306,6 @@ function elgg_deprecated_notice($msg, $dep_version, $backtrace_level = 1) {   * @return string The current page URL.   */  function current_page_url() { -	global $CONFIG; -  	$url = parse_url(elgg_get_site_url());  	$page = $url['scheme'] . "://"; @@ -1338,7 +1350,7 @@ function full_url() {  		"" : (":" . $_SERVER["SERVER_PORT"]);  	// This is here to prevent XSS in poorly written browsers used by 80% of the population. -	// {@trac [5813]} +	// https://github.com/Elgg/Elgg/commit/0c947e80f512cb0a482b1864fd0a6965c8a0cd4a  	$quotes = array('\'', '"');  	$encoded = array('%27', '%22'); @@ -1354,7 +1366,7 @@ function full_url() {   * @param array $parts       Associative array of URL components like parse_url() returns   * @param bool  $html_encode HTML Encode the url?   * - * @return str Full URL + * @return string Full URL   * @since 1.7.0   */  function elgg_http_build_url(array $parts, $html_encode = TRUE) { @@ -1385,10 +1397,10 @@ function elgg_http_build_url(array $parts, $html_encode = TRUE) {   * add tokens to the action.  The form view automatically handles   * tokens.   * - * @param str  $url         Full action URL - * @param bool $html_encode HTML encode the url? (default: false) + * @param string $url         Full action URL + * @param bool   $html_encode HTML encode the url? (default: false)   * - * @return str URL with action tokens + * @return string URL with action tokens   * @since 1.7.0   * @link http://docs.elgg.org/Tutorials/Actions   */ @@ -1440,17 +1452,17 @@ function elgg_http_remove_url_query_element($url, $element) {  	}  	$url_array['query'] = http_build_query($query); -	$string = elgg_http_build_url($url_array); +	$string = elgg_http_build_url($url_array, false);  	return $string;  }  /**   * Adds an element or elements to a URL's query string.   * - * @param str   $url      The URL - * @param array $elements Key/value pairs to add to the URL + * @param string $url      The URL + * @param array  $elements Key/value pairs to add to the URL   * - * @return str The new URL with the query strings added + * @return string The new URL with the query strings added   * @since 1.7.0   */  function elgg_http_add_url_query_elements($url, array $elements) { @@ -1487,8 +1499,6 @@ function elgg_http_add_url_query_elements($url, array $elements) {   * @since 1.8.0   */  function elgg_http_url_is_identical($url1, $url2, $ignore_params = array('offset', 'limit')) { -	global $CONFIG; -  	// if the server portion is missing but it starts with / then add the url in.  	// @todo use elgg_normalize_url()  	if (elgg_substr($url1, 0, 1) == '/') { @@ -1627,7 +1637,7 @@ $sort_type = SORT_LOCALE_STRING) {  	$sort = array(); -	foreach ($array as $k => $v) { +	foreach ($array as $v) {  		if (isset($v[$element])) {  			$sort[] = strtolower($v[$element]);  		} else { @@ -1646,7 +1656,7 @@ $sort_type = SORT_LOCALE_STRING) {   *   * @param string $ini_get_arg The INI setting   * - * @return true|false Depending on whether it's on or off + * @return bool Depending on whether it's on or off   */  function ini_get_bool($ini_get_arg) {  	$temp = strtolower(ini_get($ini_get_arg)); @@ -1662,7 +1672,7 @@ function ini_get_bool($ini_get_arg) {   *   * @tip Use this for arithmetic when determining if a file can be uploaded.   * - * @param str $setting The php.ini setting + * @param string $setting The php.ini setting   *   * @return int   * @since 1.7.0 @@ -1677,8 +1687,10 @@ function elgg_get_ini_setting_in_bytes($setting) {  	switch($last) {  		case 'g':  			$val *= 1024; +			// fallthrough intentional  		case 'm':  			$val *= 1024; +			// fallthrough intentional  		case 'k':  			$val *= 1024;  	} @@ -1835,7 +1847,7 @@ function elgg_ajax_page_handler($page) {   *   * @param array $page The page array   * - * @return void + * @return bool   * @elgg_pagehandler css   * @access private   */ @@ -1899,6 +1911,7 @@ function elgg_cacheable_view_page_handler($page, $type) {  		echo $return;  		return true;  	} +	return false;  }  /** @@ -2220,7 +2233,7 @@ function elgg_init() {   * @param array  $params empty   *   * @elgg_plugin_hook unit_tests system - * @return void + * @return array   * @access private   */  function elgg_api_test($hook, $type, $value, $params) { @@ -2232,7 +2245,10 @@ function elgg_api_test($hook, $type, $value, $params) {  }  /**#@+ - * Controlls access levels on ElggEntity entities, metadata, and annotations. + * Controls access levels on ElggEntity entities, metadata, and annotations. + * + * @warning ACCESS_DEFAULT is a place holder for the input/access view. Do not + * use it when saving an entity.   *   * @var int   */ @@ -2266,7 +2282,7 @@ define('ELGG_ENTITIES_NO_VALUE', 0);   * referring page.   *   * @see forward - * @var unknown_type + * @var int -1   */  define('REFERRER', -1); diff --git a/engine/lib/entities.php b/engine/lib/entities.php index ce736ce05..4fcf1c657 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -17,6 +17,15 @@ global $ENTITY_CACHE;  $ENTITY_CACHE = array();  /** + * GUIDs of entities banned from the entity cache (during this request) + * + * @global array $ENTITY_CACHE_DISABLED_GUIDS + * @access private + */ +global $ENTITY_CACHE_DISABLED_GUIDS; +$ENTITY_CACHE_DISABLED_GUIDS = array(); + +/**   * Cache subtypes and related class names.   *   * @global array|null $SUBTYPE_CACHE array once populated from DB, initially null @@ -26,14 +35,42 @@ global $SUBTYPE_CACHE;  $SUBTYPE_CACHE = null;  /** + * Remove this entity from the entity cache and make sure it is not re-added + * + * @param int $guid The entity guid + * + * @access private + * @todo this is a workaround until #5604 can be implemented + */ +function _elgg_disable_caching_for_entity($guid) { +	global $ENTITY_CACHE_DISABLED_GUIDS; + +	_elgg_invalidate_cache_for_entity($guid); +	$ENTITY_CACHE_DISABLED_GUIDS[$guid] = true; +} + +/** + * Allow this entity to be stored in the entity cache + * + * @param int $guid The entity guid + * + * @access private + */ +function _elgg_enable_caching_for_entity($guid) { +	global $ENTITY_CACHE_DISABLED_GUIDS; + +	unset($ENTITY_CACHE_DISABLED_GUIDS[$guid]); +} + +/**   * Invalidate this class's entry in the cache.   *   * @param int $guid The entity guid   * - * @return null + * @return void   * @access private   */ -function invalidate_cache_for_entity($guid) { +function _elgg_invalidate_cache_for_entity($guid) {  	global $ENTITY_CACHE;  	$guid = (int)$guid; @@ -50,14 +87,14 @@ function invalidate_cache_for_entity($guid) {   *   * @param ElggEntity $entity Entity to cache   * - * @return null - * @see retrieve_cached_entity() - * @see invalidate_cache_for_entity() + * @return void + * @see _elgg_retrieve_cached_entity() + * @see _elgg_invalidate_cache_for_entity()   * @access private - * TODO(evan): Use an ElggCache object + * @todo Use an ElggCache object   */ -function cache_entity(ElggEntity $entity) { -	global $ENTITY_CACHE; +function _elgg_cache_entity(ElggEntity $entity) { +	global $ENTITY_CACHE, $ENTITY_CACHE_DISABLED_GUIDS;  	// Don't cache non-plugin entities while access control is off, otherwise they could be  	// exposed to users who shouldn't see them when control is re-enabled. @@ -65,8 +102,13 @@ function cache_entity(ElggEntity $entity) {  		return;  	} +	$guid = $entity->getGUID(); +	if (isset($ENTITY_CACHE_DISABLED_GUIDS[$guid])) { +		return; +	} +  	// Don't store too many or we'll have memory problems -	// TODO(evan): Pick a less arbitrary limit +	// @todo Pick a less arbitrary limit  	if (count($ENTITY_CACHE) > 256) {  		$random_guid = array_rand($ENTITY_CACHE); @@ -79,7 +121,7 @@ function cache_entity(ElggEntity $entity) {  		elgg_get_metadata_cache()->clear($random_guid);  	} -	$ENTITY_CACHE[$entity->guid] = $entity; +	$ENTITY_CACHE[$guid] = $entity;  }  /** @@ -88,11 +130,11 @@ function cache_entity(ElggEntity $entity) {   * @param int $guid The guid   *   * @return ElggEntity|bool false if entity not cached, or not fully loaded - * @see cache_entity() - * @see invalidate_cache_for_entity() + * @see _elgg_cache_entity() + * @see _elgg_invalidate_cache_for_entity()   * @access private   */ -function retrieve_cached_entity($guid) { +function _elgg_retrieve_cached_entity($guid) {  	global $ENTITY_CACHE;  	if (isset($ENTITY_CACHE[$guid])) { @@ -105,31 +147,6 @@ function retrieve_cached_entity($guid) {  }  /** - * As retrieve_cached_entity, but returns the result as a stdClass - * (compatible with load functions that expect a database row.) - * - * @param int $guid The guid - * - * @return mixed - * @todo unused - * @access private - */ -function retrieve_cached_entity_row($guid) { -	$obj = retrieve_cached_entity($guid); -	if ($obj) { -		$tmp = new stdClass; - -		foreach ($obj as $k => $v) { -			$tmp->$k = $v; -		} - -		return $tmp; -	} - -	return false; -} - -/**   * Return the id for a given subtype.   *   * ElggEntity objects have a type and a subtype.  Subtypes @@ -432,7 +449,7 @@ function update_subtype($type, $subtype, $class = '') {   * @param int $time_created   The time creation timestamp   *   * @return bool - * @link http://docs.elgg.org/DataModel/Entities + * @throws InvalidParameterException   * @access private   */  function update_entity($guid, $owner_guid, $access_id, $container_guid = null, $time_created = null) { @@ -455,6 +472,10 @@ function update_entity($guid, $owner_guid, $access_id, $container_guid = null, $  		$time_created = (int) $time_created;  	} +	if ($access_id == ACCESS_DEFAULT) { +		throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); +	} +  	if ($entity && $entity->canEdit()) {  		if (elgg_trigger_event('update', $entity->type, $entity)) {  			$ret = update_data("UPDATE {$CONFIG->dbprefix}entities @@ -531,6 +552,7 @@ function can_write_to_container($user_guid = 0, $container_guid = 0, $type = 'al  		// If still not approved, see if the user is a member of the group  		// @todo this should be moved to the groups plugin/library  		if (!$return && $user && $container instanceof ElggGroup) { +			/* @var ElggGroup $container */  			if ($container->isMember($user)) {  				$return = true;  			} @@ -580,7 +602,6 @@ $container_guid = 0) {  	$type = sanitise_string($type);  	$subtype_id = add_subtype($type, $subtype);  	$owner_guid = (int)$owner_guid; -	$access_id = (int)$access_id;  	$time = time();  	if ($site_guid == 0) {  		$site_guid = $CONFIG->site_guid; @@ -589,6 +610,10 @@ $container_guid = 0) {  	if ($container_guid == 0) {  		$container_guid = $owner_guid;  	} +	$access_id = (int)$access_id; +	if ($access_id == ACCESS_DEFAULT) { +		throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); +	}  	$user_guid = elgg_get_logged_in_user_guid();  	if (!can_write_to_container($user_guid, $owner_guid, $type, $subtype)) { @@ -736,7 +761,7 @@ function get_entity($guid) {  	// @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 },  +	// 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') { @@ -744,7 +769,7 @@ function get_entity($guid) {  	}  	// Check local cache first -	$new_entity = retrieve_cached_entity($guid); +	$new_entity = _elgg_retrieve_cached_entity($guid);  	if ($new_entity) {  		return $new_entity;  	} @@ -766,7 +791,7 @@ function get_entity($guid) {  	if ($shared_cache) {  		$cached_entity = $shared_cache->load($guid); -		// @todo store ACLs in memcache http://trac.elgg.org/ticket/3018#comment:3 +		// @todo store ACLs in memcache https://github.com/elgg/elgg/issues/3018#issuecomment-13662617  		if ($cached_entity) {  			// @todo use ACL and cached entity access_id to determine if user can see it  			return $cached_entity; @@ -781,7 +806,7 @@ function get_entity($guid) {  	}  	if ($new_entity) { -		cache_entity($new_entity); +		_elgg_cache_entity($new_entity);  	}  	return $new_entity;  } @@ -908,6 +933,8 @@ function elgg_get_entities(array $options = array()) {  		'joins'					=>	array(),  		'callback'				=> 'entity_row_to_elggstar', + +		'__ElggBatch'			=> null,  	);  	$options = array_merge($defaults, $options); @@ -1025,7 +1052,7 @@ function elgg_get_entities(array $options = array()) {  		}  		if ($options['callback'] === 'entity_row_to_elggstar') { -			$dt = _elgg_fetch_entities_from_sql($query); +			$dt = _elgg_fetch_entities_from_sql($query, $options['__ElggBatch']);  		} else {  			$dt = get_data($query, $options['callback']);  		} @@ -1036,7 +1063,7 @@ function elgg_get_entities(array $options = 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); +					_elgg_cache_entity($item);  					// plugins usually have only settings  					if (!$item instanceof ElggPlugin) {  						$guids[] = $item->guid; @@ -1060,13 +1087,14 @@ function elgg_get_entities(array $options = array()) {  /**   * Return entities from an SQL query generated by elgg_get_entities.   * - * @param string $sql + * @param string    $sql + * @param ElggBatch $batch   * @return ElggEntity[]   *   * @access private   * @throws LogicException   */ -function _elgg_fetch_entities_from_sql($sql) { +function _elgg_fetch_entities_from_sql($sql, ElggBatch $batch = null) {  	static $plugin_subtype;  	if (null === $plugin_subtype) {  		$plugin_subtype = get_subtype_id('object', 'plugin'); @@ -1101,7 +1129,7 @@ function _elgg_fetch_entities_from_sql($sql) {  		if (empty($row->guid) || empty($row->type)) {  			throw new LogicException('Entity row missing guid or type');  		} -		if ($entity = retrieve_cached_entity($row->guid)) { +		if ($entity = _elgg_retrieve_cached_entity($row->guid)) {  			$rows[$i] = $entity;  			continue;  		} @@ -1119,16 +1147,17 @@ function _elgg_fetch_entities_from_sql($sql) {  	// Do secondary queries and merge rows  	if ($lookup_types) {  		$dbprefix = elgg_get_config('dbprefix'); -	} -	foreach ($lookup_types as $type => $guids) { -		$set = "(" . implode(',', $guids) . ")"; -		$sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set"; -		$secondary_rows = get_data($sql); -		if ($secondary_rows) { -			foreach ($secondary_rows as $secondary_row) { -				$key = $guid_to_key[$secondary_row->guid]; -				// cast to arrays to merge then cast back -				$rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row); + +		foreach ($lookup_types as $type => $guids) { +			$set = "(" . implode(',', $guids) . ")"; +			$sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set"; +			$secondary_rows = get_data($sql); +			if ($secondary_rows) { +				foreach ($secondary_rows as $secondary_row) { +					$key = $guid_to_key[$secondary_row->guid]; +					// cast to arrays to merge then cast back +					$rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row); +				}  			}  		}  	} @@ -1142,6 +1171,11 @@ function _elgg_fetch_entities_from_sql($sql) {  			} catch (IncompleteEntityException $e) {  				// don't let incomplete entities throw fatal errors  				unset($rows[$i]); + +				// report incompletes to the batch process that spawned this query +				if ($batch) { +					$batch->reportIncompleteEntity($row); +				}  			}  		}  	} @@ -1217,13 +1251,24 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair  			$subtype_ids = array();  			if ($subtypes) {  				foreach ($subtypes as $subtype) { -					// check that the subtype is valid (with ELGG_ENTITIES_NO_VALUE being a valid subtype) -					if (ELGG_ENTITIES_NO_VALUE === $subtype || $subtype_id = get_subtype_id($type, $subtype)) { -						$subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $subtype) ? ELGG_ENTITIES_NO_VALUE : $subtype_id; -					} else { -						$valid_subtypes_count--; -						elgg_log("Type-subtype '$type:$subtype' does not exist!", 'NOTICE'); +					// check that the subtype is valid +					if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) { +						// subtype value is 0 +						$subtype_ids[] = ELGG_ENTITIES_NO_VALUE; +					} elseif (!$subtype) { +						// subtype is ignored. +						// this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0  						continue; +					} else { +						$subtype_id = get_subtype_id($type, $subtype); +						 +						if ($subtype_id) { +							$subtype_ids[] = $subtype_id; +						} else { +							$valid_subtypes_count--; +							elgg_log("Type-subtype '$type:$subtype' does not exist!", 'NOTICE'); +							continue; +						}  					}  				} @@ -1428,8 +1473,10 @@ function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entiti  	global $autofeed;  	$autofeed = true; +	$offset_key = isset($options['offset_key']) ? $options['offset_key'] : 'offset'; +  	$defaults = array( -		'offset' => (int) max(get_input('offset', 0), 0), +		'offset' => (int) max(get_input($offset_key, 0), 0),  		'limit' => (int) max(get_input('limit', 10), 0),  		'full_view' => TRUE,  		'list_type_toggle' => FALSE, @@ -1459,11 +1506,13 @@ function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entiti   *   * @tip Use this to generate a list of archives by month for when entities were added or updated.   * + * @todo document how to pass in array for $subtype + *   * @warning Months are returned in the form YYYYMM.   *   * @param string $type           The type of entity   * @param string $subtype        The subtype of entity - * @param int    $container_guid The container GUID that the entinties belong to + * @param int    $container_guid The container GUID that the entities belong to   * @param int    $site_guid      The site GUID   * @param string $order_by       Order_by SQL order by clause   * @@ -1613,7 +1662,7 @@ function disable_entity($guid, $reason = "", $recursive = true) {  				$entity->disableMetadata();  				$entity->disableAnnotations(); -				invalidate_cache_for_entity($guid); +				_elgg_invalidate_cache_for_entity($guid);  				$res = update_data("UPDATE {$CONFIG->dbprefix}entities  					SET enabled = 'no' @@ -1629,8 +1678,8 @@ function disable_entity($guid, $reason = "", $recursive = true) {  /**   * Enable an entity.   * - * @warning In order to enable an entity using ElggEntity::enable(), - * you must first use {@link access_show_hidden_entities()}. + * @warning In order to enable an entity, you must first use + * {@link access_show_hidden_entities()}.   *   * @param int  $guid      GUID of entity to enable   * @param bool $recursive Recursively enable all entities disabled with the entity? @@ -1711,7 +1760,7 @@ function delete_entity($guid, $recursive = true) {  				// delete cache  				if (isset($ENTITY_CACHE[$guid])) { -					invalidate_cache_for_entity($guid); +					_elgg_invalidate_cache_for_entity($guid);  				}  				// If memcache is available then delete this entry from the cache @@ -1758,6 +1807,10 @@ function delete_entity($guid, $recursive = true) {  					elgg_set_ignore_access($ia);  				} +				$entity_disable_override = access_get_show_hidden_status(); +				access_show_hidden_entities(true); +				$ia = elgg_set_ignore_access(true); +  				// Now delete the entity itself  				$entity->deleteMetadata();  				$entity->deleteOwnedMetadata(); @@ -1765,6 +1818,9 @@ function delete_entity($guid, $recursive = true) {  				$entity->deleteOwnedAnnotations();  				$entity->deleteRelationships(); +				access_show_hidden_entities($entity_disable_override); +				elgg_set_ignore_access($ia); +  				elgg_delete_river(array('subject_guid' => $guid));  				elgg_delete_river(array('object_guid' => $guid));  				remove_all_private_settings($guid); @@ -2072,7 +2128,7 @@ function can_edit_entity_metadata($entity_guid, $user_guid = 0, $metadata = null  		$return = null; -		if ($metadata->owner_guid == 0) { +		if ($metadata && ($metadata->owner_guid == 0)) {  			$return = true;  		}  		if (is_null($return)) { @@ -2413,6 +2469,7 @@ function elgg_instanceof($entity, $type = NULL, $subtype = NULL, $class = NULL)  	$return = ($entity instanceof ElggEntity);  	if ($type) { +		/* @var ElggEntity $entity */  		$return = $return && ($entity->getType() == $type);  	} @@ -2472,11 +2529,18 @@ function update_entity_last_action($guid, $posted = NULL) {  function entities_gc() {  	global $CONFIG; -	$tables = array ('sites_entity', 'objects_entity', 'groups_entity', 'users_entity'); +	$tables = array( +		'site' => 'sites_entity', +		'object' => 'objects_entity', +		'group' => 'groups_entity', +		'user' => 'users_entity' +	); -	foreach ($tables as $table) { -		delete_data("DELETE from {$CONFIG->dbprefix}{$table} -			where guid NOT IN (SELECT guid from {$CONFIG->dbprefix}entities)"); +	foreach ($tables as $type => $table) { +		delete_data("DELETE FROM {$CONFIG->dbprefix}{$table} +			WHERE guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}entities)"); +		delete_data("DELETE FROM {$CONFIG->dbprefix}entities +			WHERE type = '$type' AND guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}{$table})");  	}  } diff --git a/engine/lib/export.php b/engine/lib/export.php index ae9be95ce..ecc894e63 100644 --- a/engine/lib/export.php +++ b/engine/lib/export.php @@ -11,7 +11,7 @@   *   * @param mixed $object The object either an ElggEntity, ElggRelationship or ElggExtender   * - * @return the UUID or false + * @return string|false the UUID or false   */  function get_uuid_from_object($object) {  	if ($object instanceof ElggEntity) { @@ -40,8 +40,6 @@ function get_uuid_from_object($object) {   * @return string   */  function guid_to_uuid($guid) { -	global $CONFIG; -  	return elgg_get_site_url()  . "export/opendd/$guid/";  } @@ -53,8 +51,6 @@ function guid_to_uuid($guid) {   * @return bool   */  function is_uuid_this_domain($uuid) { -	global $CONFIG; -  	if (strpos($uuid, elgg_get_site_url()) === 0) {  		return true;  	} @@ -67,7 +63,7 @@ function is_uuid_this_domain($uuid) {   *   * @param string $uuid A unique ID   * - * @return mixed + * @return ElggEntity|false   */  function get_entity_from_uuid($uuid) {  	$uuid = sanitise_string($uuid); @@ -117,18 +113,19 @@ function _process_element(ODD $odd) {  	global $IMPORTED_DATA, $IMPORTED_OBJECT_COUNTER;  	// See if anyone handles this element, return true if it is. +	$to_be_serialised = null;  	if ($odd) {  		$handled = elgg_trigger_plugin_hook("import", "all", array("element" => $odd), $to_be_serialised); -	} -	// If not, then see if any of its sub elements are handled -	if ($handled) { -		// Increment validation counter -		$IMPORTED_OBJECT_COUNTER ++; -		// Return the constructed object -		$IMPORTED_DATA[] = $handled; +		// If not, then see if any of its sub elements are handled +		if ($handled) { +			// Increment validation counter +			$IMPORTED_OBJECT_COUNTER ++; +			// Return the constructed object +			$IMPORTED_DATA[] = $handled; -		return true; +			return true; +		}  	}  	return false; @@ -167,7 +164,7 @@ function exportAsArray($guid) {   *   * @param int $guid The GUID.   * - * @return xml + * @return string XML   * @see ElggEntity for an example of its usage.   * @access private   */ @@ -184,7 +181,7 @@ function export($guid) {   * @param string $xml XML string   *   * @return bool - * @throws Exception if there was a problem importing the data. + * @throws ImportException if there was a problem importing the data.   * @access private   */  function import($xml) { diff --git a/engine/lib/extender.php b/engine/lib/extender.php index 538f601e1..8323bd3ce 100644 --- a/engine/lib/extender.php +++ b/engine/lib/extender.php @@ -86,6 +86,7 @@ function oddmetadata_to_elggextender(ElggEntity $entity, ODDMetaData $element) {   * @return null   * @elgg_plugin_hook_handler volatile metadata   * @todo investigate more. + * @throws ImportException   * @access private   */  function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) { @@ -94,6 +95,7 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params)  	$tmp = NULL;  	if ($element instanceof ODDMetaData) { +		/* @var ODDMetaData $element */  		// Recall entity  		$entity_uuid = $element->getAttribute('entity_uuid');  		$entity = get_entity_from_uuid($entity_uuid); @@ -124,14 +126,20 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params)   * @return bool   */  function can_edit_extender($extender_id, $type, $user_guid = 0) { -	if (!elgg_is_logged_in()) { -		return false; +	// @todo Since Elgg 1.0, Elgg has returned false from can_edit_extender() +	// if no user was logged in. This breaks the access override. This is a +	// temporary work around. This function needs to be rewritten in Elgg 1.9  +	if (!elgg_check_access_overrides($user_guid)) { +		if (!elgg_is_logged_in()) { +			return false; +		}  	}  	$user_guid = (int)$user_guid; -	$user = get_entity($user_guid); +	$user = get_user($user_guid);  	if (!$user) {  		$user = elgg_get_logged_in_user_entity(); +		$user_guid = elgg_get_logged_in_user_guid();  	}  	$functionname = "elgg_get_{$type}_from_id"; @@ -147,16 +155,16 @@ function can_edit_extender($extender_id, $type, $user_guid = 0) {  	/* @var ElggExtender $extender */  	// If the owner is the specified user, great! They can edit. -	if ($extender->getOwnerGUID() == $user->getGUID()) { +	if ($extender->getOwnerGUID() == $user_guid) {  		return true;  	}  	// If the user can edit the entity this is attached to, great! They can edit. -	if (can_edit_entity($extender->entity_guid, $user->getGUID())) { +	if (can_edit_entity($extender->entity_guid, $user_guid)) {  		return true;  	} -	// Trigger plugin hooks +	// Trigger plugin hook - note that $user may be null  	$params = array('entity' => $extender->getEntity(), 'user' => $user);  	return elgg_trigger_plugin_hook('permissions_check', $type, $params, false);  } diff --git a/engine/lib/filestore.php b/engine/lib/filestore.php index 93a127257..a3c7ba439 100644 --- a/engine/lib/filestore.php +++ b/engine/lib/filestore.php @@ -308,8 +308,6 @@ function get_image_resize_parameters($width, $height, $options) {  function file_delete($guid) {  	if ($file = get_entity($guid)) {  		if ($file->canEdit()) { -			$container = get_entity($file->container_guid); -  			$thumbnail = $file->thumbnail;  			$smallthumb = $file->smallthumb;  			$largethumb = $file->largethumb; @@ -383,7 +381,7 @@ function file_get_general_file_type($mimetype) {  /**   * Delete a directory and all its contents   * - * @param str $directory Directory to delete + * @param string $directory Directory to delete   *   * @return bool   */ @@ -500,7 +498,7 @@ function filestore_init() {  /**   * Unit tests for files   * - * @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/group.php b/engine/lib/group.php index 5a38e1ea6..6ded8a825 100644 --- a/engine/lib/group.php +++ b/engine/lib/group.php @@ -170,7 +170,7 @@ function get_group_members($group_guid, $limit = 10, $offset = 0, $site_guid = 0  		'relationship' => 'member',  		'relationship_guid' => $group_guid,  		'inverse_relationship' => TRUE, -		'types' => 'user', +		'type' => 'user',  		'limit' => $limit,  		'offset' => $offset,  		'count' => $count, @@ -240,9 +240,11 @@ function leave_group($group_guid, $user_guid) {   */  function get_users_membership($user_guid) {  	$options = array( +		'type' => 'group',  		'relationship' => 'member',  		'relationship_guid' => $user_guid, -		'inverse_relationship' => FALSE +		'inverse_relationship' => false, +		'limit' => false,  	);  	return elgg_get_entities_from_relationship($options);  } diff --git a/engine/lib/input.php b/engine/lib/input.php index 6d1646e1a..80b0b8766 100644 --- a/engine/lib/input.php +++ b/engine/lib/input.php @@ -60,8 +60,8 @@ function get_input($variable, $default = NULL, $filter_result = TRUE) {   *   * Note: this function does not handle nested arrays (ex: form input of param[m][n])   * - * @param string $variable The name of the variable - * @param string $value    The value of the variable + * @param string          $variable The name of the variable + * @param string|string[] $value    The value of the variable   *   * @return void   */ @@ -226,6 +226,8 @@ function elgg_clear_sticky_value($form_name, $variable) {  /**   * Page handler for autocomplete endpoint.   * + * @todo split this into functions/objects, this is way too big + *   * /livesearch?q=<query>   *   * Other options include: @@ -233,6 +235,7 @@ function elgg_clear_sticky_value($form_name, $variable) {   *     match_owner int    0/1   *     limit       int    default is 10   * + * @param array $page   * @return string JSON string is returned and then exit   * @access private   */ @@ -265,10 +268,8 @@ function input_livesearch_page_handler($page) {  	}  	if (get_input('match_owner', false)) { -		$owner_guid = $user->getGUID();  		$owner_where = 'AND e.owner_guid = ' . $user->getGUID();  	} else { -		$owner_guid = null;  		$owner_where = '';  	} @@ -289,7 +290,9 @@ function input_livesearch_page_handler($page) {  				if ($entities = get_data($query)) {  					foreach ($entities as $entity) { +						// @todo use elgg_get_entities (don't query in a loop!)  						$entity = get_entity($entity->guid); +						/* @var ElggUser $entity */  						if (!$entity) {  							continue;  						} @@ -338,7 +341,9 @@ function input_livesearch_page_handler($page) {  				";  				if ($entities = get_data($query)) {  					foreach ($entities as $entity) { +						// @todo use elgg_get_entities (don't query in a loop!)  						$entity = get_entity($entity->guid); +						/* @var ElggGroup $entity */  						if (!$entity) {  							continue;  						} @@ -385,7 +390,9 @@ function input_livesearch_page_handler($page) {  				if ($entities = get_data($query)) {  					foreach ($entities as $entity) { +						// @todo use elgg_get_entities (don't query in a loop!)  						$entity = get_entity($entity->guid); +						/* @var ElggUser $entity */  						if (!$entity) {  							continue;  						} diff --git a/engine/lib/languages.php b/engine/lib/languages.php index 98006f7cd..61ba91ddb 100644 --- a/engine/lib/languages.php +++ b/engine/lib/languages.php @@ -77,7 +77,7 @@ function elgg_echo($message_key, $args = array(), $language = "") {   * @param string $country_code   Standard country code (eg 'en', 'nl', 'es')   * @param array  $language_array Formatted array of strings   * - * @return true|false Depending on success + * @return bool Depending on success   */  function add_translation($country_code, $language_array) {  	global $CONFIG; @@ -104,8 +104,6 @@ function add_translation($country_code, $language_array) {   * @return string The language code for the site/user or "en" if not set   */  function get_current_language() { -	global $CONFIG; -  	$language = get_language();  	if (!$language) { @@ -141,6 +139,9 @@ function get_language() {  	return false;  } +/** + * @access private + */  function _elgg_load_translations() {  	global $CONFIG; @@ -148,7 +149,7 @@ function _elgg_load_translations() {  		$loaded = true;  		$languages = array_unique(array('en', get_current_language()));  		foreach ($languages as $language) { -			$data = elgg_load_system_cache("$language.php"); +			$data = elgg_load_system_cache("$language.lang");  			if ($data) {  				add_translation($language, unserialize($data));  			} else { @@ -177,7 +178,7 @@ function _elgg_load_translations() {   * @param bool   $load_all If true all languages are loaded, if   *                         false only the current language + en are loaded   * - * @return void + * @return bool success   */  function register_translations($path, $load_all = false) {  	global $CONFIG; @@ -229,23 +230,37 @@ function register_translations($path, $load_all = false) {  /**   * Reload all translations from all registered paths.   * - * This is only called by functions which need to know all possible translations, namely the - * statistic gathering ones. + * This is only called by functions which need to know all possible translations.   *   * @todo Better on demand loading based on language_paths array   * - * @return bool + * @return void   */  function reload_all_translations() {  	global $CONFIG;  	static $LANG_RELOAD_ALL_RUN;  	if ($LANG_RELOAD_ALL_RUN) { -		return null; +		return;  	} -	foreach ($CONFIG->language_paths as $path => $dummy) { -		register_translations($path, true); +	if ($CONFIG->i18n_loaded_from_cache) { +		$cache = elgg_get_system_cache(); +		$cache_dir = $cache->getVariable("cache_path"); +		$filenames = elgg_get_file_list($cache_dir, array(), array(), array(".lang")); +		foreach ($filenames as $filename) { +			if (preg_match('/([a-z]+)\.[^.]+$/', $filename, $matches)) { +				$language = $matches[1]; +				$data = elgg_load_system_cache("$language.lang"); +				if ($data) { +					add_translation($language, unserialize($data)); +				} +			} +		} +	} else { +		foreach ($CONFIG->language_paths as $path => $dummy) { +			register_translations($path, true); +		}  	}  	$LANG_RELOAD_ALL_RUN = true; @@ -337,14 +352,3 @@ function get_missing_language_keys($language) {  	return false;  } - -/** - * Initialize the language library - * @access private - */ -function elgg_languages_init() { -	$lang = get_current_language(); -	elgg_register_simplecache_view("js/languages/$lang"); -} - -elgg_register_event_handler('init', 'system', 'elgg_languages_init'); diff --git a/engine/lib/location.php b/engine/lib/location.php index 5b889509b..1534c7d7b 100644 --- a/engine/lib/location.php +++ b/engine/lib/location.php @@ -101,7 +101,7 @@ function elgg_get_entities_from_location(array $options = array()) {  	$long_min = $long - $long_distance;  	$long_max = $long + $long_distance; -	$where = array(); +	$wheres = array();  	$wheres[] = "lat_name.string='geo:lat'";  	$wheres[] = "lat_value.string >= $lat_min";  	$wheres[] = "lat_value.string <= $lat_max"; @@ -139,7 +139,7 @@ function elgg_get_entities_from_location(array $options = array()) {  /**   * Returns a viewable list of entities from location   * - * @param array $options + * @param array $options Options array   *   * @see elgg_list_entities()   * @see elgg_get_entities_from_location() diff --git a/engine/lib/mb_wrapper.php b/engine/lib/mb_wrapper.php index c2f5503e0..68fa69005 100644 --- a/engine/lib/mb_wrapper.php +++ b/engine/lib/mb_wrapper.php @@ -11,7 +11,7 @@ if (is_callable('mb_internal_encoding')) {   * NOTE: This differs from parse_str() by returning the results   * instead of placing them in the local scope!   * - * @param str $str The string + * @param string $str The string   *   * @return array   * @since 1.7.0 diff --git a/engine/lib/memcache.php b/engine/lib/memcache.php index f79fba4a9..79b87e850 100644 --- a/engine/lib/memcache.php +++ b/engine/lib/memcache.php @@ -35,3 +35,23 @@ function is_memcache_available() {  	return $memcache_available;  } + +/** + * Invalidate an entity in memcache + * + * @param int $entity_guid The GUID of the entity to invalidate + * + * @return void + * @access private + */ +function _elgg_invalidate_memcache_for_entity($entity_guid) { +	static $newentity_cache; +	
 +	if ((!$newentity_cache) && (is_memcache_available())) {
 +		$newentity_cache = new ElggMemcache('new_entity_cache');
 +	} +	
 +	if ($newentity_cache) {
 +		$newentity_cache->delete($entity_guid);
 +	} +}
\ No newline at end of file diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php index f76c20f24..fdb1b85f6 100644 --- a/engine/lib/metadata.php +++ b/engine/lib/metadata.php @@ -191,19 +191,19 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i  	}  	// Add the metastring -	$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 = "UPDATE {$CONFIG->dbprefix}metadata" -		. " set name_id='$name', value_id='$value', value_type='$value_type', access_id=$access_id," +		. " set name_id='$name_id', value_id='$value_id', value_type='$value_type', access_id=$access_id,"  		. " owner_guid=$owner_guid where id=$id";  	$result = update_data($query); @@ -277,10 +277,18 @@ $access_id = ACCESS_PRIVATE, $allow_multiple = false) {   *                                   all metadata that match the query instead of returning   *                                   ElggMetadata objects.   * - * @return mixed + * @return ElggMetadata[]|mixed   * @since 1.8.0   */  function elgg_get_metadata(array $options = array()) { + +	// @todo remove support for count shortcut - see #4393 +	// support shortcut of 'count' => true for 'metadata_calculation' => 'count' +	if (isset($options['count']) && $options['count']) { +		$options['metadata_calculation'] = 'count'; +		unset($options['count']); +	} +  	$options['metastring_type'] = 'metadata';  	return elgg_get_metastring_based_objects($options);  } @@ -292,21 +300,22 @@ function elgg_get_metadata(array $options = array()) {   *          This requires at least one constraint: metadata_owner_guid(s),   *          metadata_name(s), metadata_value(s), or guid(s) must be set.   * - * @warning This returns null on no ops. - *   * @param array $options An options array. {@see elgg_get_metadata()} - * @return mixed Null if the metadata name is invalid. Bool on success or fail. + * @return bool|null true on success, false on failure, null if no metadata to delete.   * @since 1.8.0   */  function elgg_delete_metadata(array $options) {  	if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) {  		return false;  	} +	$options['metastring_type'] = 'metadata'; +	$result = elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); +	// This moved last in case an object's constructor sets metadata. Currently the batch +	// delete process has to create the entity to delete its metadata. See #5214  	elgg_get_metadata_cache()->invalidateByOptions('delete', $options); -	$options['metastring_type'] = 'metadata'; -	return elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); +	return $result;  }  /** @@ -314,10 +323,8 @@ function elgg_delete_metadata(array $options) {   *   * @warning Unlike elgg_get_metadata() this will not accept an empty options array!   * - * @warning This returns null on no ops. - *   * @param array $options An options array. {@See elgg_get_metadata()} - * @return mixed + * @return bool|null true on success, false on failure, null if no metadata disabled.   * @since 1.8.0   */  function elgg_disable_metadata(array $options) { @@ -326,9 +333,13 @@ function elgg_disable_metadata(array $options) {  	}  	elgg_get_metadata_cache()->invalidateByOptions('disable', $options); +	 +	// if we can see hidden (disabled) we need to use the offset +	// otherwise we risk an infinite loop if there are more than 50 +	$inc_offset = access_get_show_hidden_status();  	$options['metastring_type'] = 'metadata'; -	return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', false); +	return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset);  }  /** @@ -336,10 +347,11 @@ function elgg_disable_metadata(array $options) {   *   * @warning Unlike elgg_get_metadata() this will not accept an empty options array!   * - * @warning This returns null on no ops. + * @warning In order to enable metadata, you must first use + * {@link access_show_hidden_entities()}.   *   * @param array $options An options array. {@See elgg_get_metadata()} - * @return mixed + * @return bool|null true on success, false on failure, null if no metadata enabled.   * @since 1.8.0   */  function elgg_enable_metadata(array $options) { @@ -394,9 +406,11 @@ function elgg_enable_metadata(array $options) {   *                                         'operand' => '=',   *                                         'case_sensitive' => TRUE   *                                        ) - * 	                             Currently if multiple values are sent via + *                               Currently if multiple values are sent via   *                               an array (value => array('value1', 'value2')   *                               the pair's operand will be forced to "IN". + *                               If passing "IN" as the operand and a string as the value,  + *                               the value must be a properly quoted and escaped string.   *   * 	metadata_name_value_pairs_operator => NULL|STR The operator to use for combining   *                                        (name = value) OPERATOR (name = value); default AND @@ -412,7 +426,7 @@ function elgg_enable_metadata(array $options) {   *   *  metadata_owner_guids => NULL|ARR guids for metadata owners   * - * @return mixed If count, int. If not count, array. false on errors. + * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors.   * @since 1.7.0   */  function elgg_get_entities_from_metadata(array $options = array()) { @@ -461,7 +475,7 @@ function elgg_get_entities_from_metadata(array $options = array()) {   * @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') + * @return false|array False on fail, array('joins', 'wheres')   * @since 1.7.0   * @access private   */ @@ -608,6 +622,8 @@ $owner_guids = NULL) {  			// if the operand is IN don't quote it because quoting should be done already.  			if (is_numeric($pair['value'])) {  				$value = sanitise_string($pair['value']); +			} else if (is_bool($pair['value'])) { +				$value = (int) $pair['value'];  			} else if (is_array($pair['value'])) {  				$values_array = array(); @@ -774,10 +790,10 @@ function string_to_tag_array($string) {  		$ar = explode(",", $string);  		$ar = array_map('trim', $ar);  		$ar = array_filter($ar, 'is_not_null'); +		$ar = array_map('strip_tags', $ar);  		return $ar;  	}  	return false; -  }  /** @@ -909,8 +925,8 @@ function elgg_get_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 + * @param array  $options Options passed to elgg_(delete|disable|enable)_metadata + * @return void   */  function elgg_invalidate_metadata_cache($action, array $options) {  	// remove as little as possible, optimizing for common cases diff --git a/engine/lib/metastrings.php b/engine/lib/metastrings.php index cf6dd4d98..57d876c06 100644 --- a/engine/lib/metastrings.php +++ b/engine/lib/metastrings.php @@ -67,7 +67,7 @@ function get_metastring_id($string, $case_sensitive = TRUE) {  	}  	$row = FALSE; -	$metaStrings = get_data($query, "entity_row_to_elggstar"); +	$metaStrings = get_data($query);  	if (is_array($metaStrings)) {  		if (sizeof($metaStrings) > 1) {  			$ids = array(); @@ -389,11 +389,6 @@ function elgg_get_metastring_based_objects($options) {  	$selects = $options['selects']; -	// allow count shortcut -	if ($options['count']) { -		$options['metastring_calculation'] = 'count'; -	} -  	// For performance reasons we don't want the joins required for metadata / annotations  	// unless we're going through one of their callbacks.  	// this means we expect the functions passing different callbacks to pass their required joins. @@ -426,9 +421,11 @@ function elgg_get_metastring_based_objects($options) {  	if ($metastring_clauses) {  		$wheres = array_merge($wheres, $metastring_clauses['wheres']);  		$joins = array_merge($joins, $metastring_clauses['joins']); +	} else { +		$wheres[] = get_access_sql_suffix('n_table');  	} -	if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE) { +	if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) {  		$selects = array_unique($selects);  		// evalutate selects  		$select_str = ''; @@ -439,6 +436,9 @@ function elgg_get_metastring_based_objects($options) {  		}  		$query = "SELECT DISTINCT n_table.*{$select_str} FROM {$db_prefix}$type n_table"; +	} elseif ($options['count']) { +		// count is over the entities +		$query = "SELECT count(DISTINCT e.guid) as calculation FROM {$db_prefix}$type n_table";  	} else {  		$query = "SELECT {$options['metastring_calculation']}(v.string) as calculation FROM {$db_prefix}$type n_table";  	} @@ -467,7 +467,7 @@ function elgg_get_metastring_based_objects($options) {  			$defaults['order_by']);  	} -	if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE) { +	if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) {  		if (isset($options['group_by'])) {  			$options['group_by'] = sanitise_string($options['group_by']);  			$query .= " GROUP BY {$options['group_by']}"; @@ -515,21 +515,16 @@ function elgg_get_metastring_sql($table, $names = null, $values = null,  		&& !$ids  		&& (!$pairs && $pairs !== 0)) { -		return ''; +		return array();  	}  	$db_prefix = elgg_get_config('dbprefix'); -	// join counter for incremental joins. -	$i = 1; -  	// binary forces byte-to-byte comparision of strings, making  	// it case- and diacritical-mark- sensitive.  	// only supported on values.  	$binary = ($case_sensitive) ? ' BINARY ' : ''; -	$access = get_access_sql_suffix($table); -  	$return = array (  		'joins' => array (),  		'wheres' => array() @@ -594,13 +589,15 @@ function elgg_get_metastring_sql($table, $names = null, $values = null,  	}  	if ($names_where && $values_where) { -		$wheres[] = "($names_where AND $values_where AND $access)"; +		$wheres[] = "($names_where AND $values_where)";  	} elseif ($names_where) { -		$wheres[] = "($names_where AND $access)"; +		$wheres[] = $names_where;  	} elseif ($values_where) { -		$wheres[] = "($values_where AND $access)"; +		$wheres[] = $values_where;  	} +	$wheres[] = get_access_sql_suffix($table); +  	if ($where = implode(' AND ', $wheres)) {  		$return['wheres'][] = "($where)";  	} @@ -663,9 +660,10 @@ function elgg_normalize_metastrings_options(array $options = array()) {   *   * @param int    $id      The object's ID   * @param string $enabled Value to set to: yes or no - * @param string $type    The type of table to use: metadata or anntations + * @param string $type    The type of table to use: metadata or annotations   *   * @return bool + * @throws InvalidParameterException   * @since 1.8.0   * @access private   */ @@ -740,7 +738,7 @@ function elgg_batch_metastring_based_objects(array $options, $callback, $inc_off   *   * @param int    $id   The metastring-based object's ID   * @param string $type The type: annotation or metadata - * @return mixed + * @return ElggMetadata|ElggAnnotation   *   * @since 1.8.0   * @access private @@ -806,6 +804,7 @@ function elgg_delete_metastring_based_object_by_id($id, $type) {  			}  			if ($metabyname_memcache) { +				// @todo why name_id? is that even populated?  				$metabyname_memcache->delete("{$obj->entity_guid}:{$obj->name_id}");  			}  		} diff --git a/engine/lib/navigation.php b/engine/lib/navigation.php index 86624cd7c..ab9cc05e8 100644 --- a/engine/lib/navigation.php +++ b/engine/lib/navigation.php @@ -126,6 +126,7 @@ function elgg_unregister_menu_item($menu_name, $item_name) {  	}  	foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) { +		/* @var ElggMenuItem $menu_object */  		if ($menu_object->getName() == $item_name) {  			unset($CONFIG->menus[$menu_name][$index]);  			return true; @@ -151,7 +152,8 @@ function elgg_is_menu_item_registered($menu_name, $item_name) {  		return false;  	} -	foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) { +	foreach ($CONFIG->menus[$menu_name] as $menu_object) { +		/* @var ElggMenuItem $menu_object */  		if ($menu_object->getName() == $item_name) {  			return true;  		} @@ -216,7 +218,7 @@ function elgg_push_breadcrumb($title, $link = NULL) {  	}  	// avoid key collisions. -	$CONFIG->breadcrumbs[] = array('title' => $title, 'link' => $link); +	$CONFIG->breadcrumbs[] = array('title' => elgg_get_excerpt($title, 100), 'link' => $link);  }  /** @@ -311,8 +313,8 @@ function elgg_site_menu_setup($hook, $type, $return, $params) {  	// check if we have anything selected  	$selected = false; -	foreach ($return as $section_name => $section) { -		foreach ($section as $key => $item) { +	foreach ($return as $section) { +		foreach ($section as $item) {  			if ($item->getSelected()) {  				$selected = true;  				break 2; @@ -321,7 +323,8 @@ function elgg_site_menu_setup($hook, $type, $return, $params) {  	}  	if (!$selected) { -		// nothing selected, match name to context +		// nothing selected, match name to context or match url +		$current_url = current_page_url();  		foreach ($return as $section_name => $section) {  			foreach ($section as $key => $item) {  				// only highlight internal links @@ -330,6 +333,10 @@ function elgg_site_menu_setup($hook, $type, $return, $params) {  						$return[$section_name][$key]->setSelected(true);  						break 2;  					} +					if ($item->getHref() == $current_url) { +						$return[$section_name][$key]->setSelected(true); +						break 2; +					}  				}  			}  		} @@ -345,6 +352,7 @@ function elgg_site_menu_setup($hook, $type, $return, $params) {  function elgg_river_menu_setup($hook, $type, $return, $params) {  	if (elgg_is_logged_in()) {  		$item = $params['item']; +		/* @var ElggRiverItem $item */  		$object = $item->getObjectEntity();  		// comments and non-objects cannot be commented on or liked  		if (!elgg_in_context('widgets') && $item->annotation_id == 0) { @@ -388,6 +396,7 @@ function elgg_entity_menu_setup($hook, $type, $return, $params) {  	}  	$entity = $params['entity']; +	/* @var ElggEntity $entity */  	$handler = elgg_extract('handler', $params, false);  	// access @@ -433,6 +442,7 @@ function elgg_entity_menu_setup($hook, $type, $return, $params) {  function elgg_widget_menu_setup($hook, $type, $return, $params) {  	$widget = $params['entity']; +	/* @var ElggWidget $widget */  	$show_edit = elgg_extract('show_edit', $params, true);  	$collapse = array( @@ -481,6 +491,7 @@ function elgg_widget_menu_setup($hook, $type, $return, $params) {   */  function elgg_annotation_menu_setup($hook, $type, $return, $params) {  	$annotation = $params['annotation']; +	/* @var ElggAnnotation $annotation */  	if ($annotation->name == 'generic_comment' && $annotation->canEdit()) {  		$url = elgg_http_add_url_query_elements('action/comments/delete', array( diff --git a/engine/lib/notification.php b/engine/lib/notification.php index 9e3c075a8..be0c359d4 100644 --- a/engine/lib/notification.php +++ b/engine/lib/notification.php @@ -86,7 +86,7 @@ function unregister_notification_handler($method) {   * @throws NotificationException   */  function notify_user($to, $from, $subject, $message, array $params = NULL, $methods_override = "") { -	global $NOTIFICATION_HANDLERS, $CONFIG; +	global $NOTIFICATION_HANDLERS;  	// Sanitise  	if (!is_array($to)) { @@ -110,12 +110,15 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth  			// Are we overriding delivery?  			$methods = $methods_override;  			if (!$methods) { -				$tmp = (array)get_user_notification_settings($guid); +				$tmp = get_user_notification_settings($guid);  				$methods = array(); -				foreach ($tmp as $k => $v) { -					// Add method if method is turned on for user! -					if ($v) { -						$methods[] = $k; +				// $tmp may be false. don't cast +				if (is_object($tmp)) { +					foreach ($tmp as $k => $v) { +						// Add method if method is turned on for user! +						if ($v) { +							$methods[] = $k; +						}  					}  				}  			} @@ -165,7 +168,7 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth   *   * @param int $user_guid The user id   * - * @return stdClass + * @return stdClass|false   */  function get_user_notification_settings($user_guid = 0) {  	$user_guid = (int)$user_guid; @@ -174,7 +177,8 @@ function get_user_notification_settings($user_guid = 0) {  		$user_guid = elgg_get_logged_in_user_guid();  	} -	// @todo: holy crap, really? +	// @todo: there should be a better way now that metadata is cached. E.g. just query for MD names, then +	// query user object directly  	$all_metadata = elgg_get_metadata(array(  		'guid' => $user_guid,  		'limit' => 0 @@ -237,6 +241,7 @@ function set_user_notification_setting($user_guid, $method, $value) {   * @param array      $params  Optional parameters (none taken in this instance)   *   * @return bool + * @throws NotificationException   * @access private   */  function email_notify_handler(ElggEntity $from, ElggUser $to, $subject, $message, @@ -263,7 +268,7 @@ array $params = NULL) {  	$to = $to->email;  	// From -	$site = get_entity($CONFIG->site_guid); +	$site = elgg_get_site_entity();  	// If there's an email address, use it - but only if its not from a user.  	if (!($from instanceof ElggUser) && $from->email) {  		$from = $from->email; @@ -288,6 +293,7 @@ array $params = NULL) {   * @param array  $params  Optional parameters (none used in this function)   *   * @return bool + * @throws NotificationException   * @since 1.7.2   */  function elgg_send_email($from, $to, $subject, $body, array $params = NULL) { @@ -344,6 +350,8 @@ function elgg_send_email($from, $to, $subject, $body, array $params = NULL) {  	// Sanitise subject by stripping line endings  	$subject = preg_replace("/(\r\n|\r|\n)/", " ", $subject); +	// this is because Elgg encodes everything and matches what is done with body +	$subject = html_entity_decode($subject, ENT_COMPAT, 'UTF-8'); // Decode any html entities  	if (is_callable('mb_encode_mimeheader')) {  		$subject = mb_encode_mimeheader($subject, "UTF-8", "B");  	} @@ -422,7 +430,7 @@ function register_notification_object($entity_type, $object_subtype, $language_n   * @param int $user_guid   The GUID of the user who wants to follow a user's content   * @param int $author_guid The GUID of the user whose content the user wants to follow   * - * @return true|false Depending on success + * @return bool Depending on success   */  function register_notification_interest($user_guid, $author_guid) {  	return add_entity_relationship($user_guid, 'notify', $author_guid); @@ -434,7 +442,7 @@ function register_notification_interest($user_guid, $author_guid) {   * @param int $user_guid   The GUID of the user who is following a user's content   * @param int $author_guid The GUID of the user whose content the user wants to unfollow   * - * @return true|false Depending on success + * @return bool Depending on success   */  function remove_notification_interest($user_guid, $author_guid) {  	return remove_entity_relationship($user_guid, 'notify', $author_guid); @@ -450,12 +458,13 @@ function remove_notification_interest($user_guid, $author_guid) {   * @param string $object_type mixed   * @param mixed  $object      The object created   * - * @return void + * @return bool   * @access private   */  function object_notifications($event, $object_type, $object) {  	// We only want to trigger notification events for ElggEntities  	if ($object instanceof ElggEntity) { +		/* @var ElggEntity $object */  		// Get config data  		global $CONFIG, $SESSION, $NOTIFICATION_HANDLERS; @@ -492,9 +501,10 @@ function object_notifications($event, $object_type, $object) {  					'relationship' => 'notify' . $method,  					'relationship_guid' => $object->container_guid,  					'inverse_relationship' => TRUE, -					'types' => 'user', -					'limit' => 99999 +					'type' => 'user', +					'limit' => false  				)); +				/* @var ElggUser[] $interested_users */  				if ($interested_users && is_array($interested_users)) {  					foreach ($interested_users as $user) { diff --git a/engine/lib/objects.php b/engine/lib/objects.php index e5e8f67c4..ff3cc733f 100644 --- a/engine/lib/objects.php +++ b/engine/lib/objects.php @@ -93,16 +93,16 @@ function get_object_sites($object_guid, $limit = 10, $offset = 0) {  	return elgg_get_entities_from_relationship(array(  		'relationship' => 'member_of_site',  		'relationship_guid' => $object_guid, -		'types' => 'site', +		'type' => 'site',  		'limit' => $limit, -		'offset' => $offset +		'offset' => $offset,  	));  }  /**   * Runs unit tests for ElggObject   * - * @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/opendd.php b/engine/lib/opendd.php index f00ea6aab..7d635a295 100644 --- a/engine/lib/opendd.php +++ b/engine/lib/opendd.php @@ -7,6 +7,8 @@   * @version 0.4   */ +// @codingStandardsIgnoreStart +  /**   * Attempt to construct an ODD object out of a XmlElement or sub-elements.   * @@ -103,3 +105,5 @@ function ODD_Import($xml) {  function ODD_Export(ODDDocument $document) {  	return "$document";  } + +// @codingStandardsIgnoreEnd diff --git a/engine/lib/output.php b/engine/lib/output.php index 9295f2173..de4f911fb 100644 --- a/engine/lib/output.php +++ b/engine/lib/output.php @@ -12,29 +12,34 @@   *   * @param string $text The input string   * - * @return string The output stirng with formatted links - **/ + * @return string The output string with formatted links + */  function parse_urls($text) { + +	// URI specification: http://www.ietf.org/rfc/rfc3986.txt +	// This varies from the specification in the following ways: +	//  * Supports non-ascii characters +	//  * Does not allow parentheses and single quotes +	//  * Cuts off commas, exclamation points, and periods off as last character +  	// @todo this causes problems with <attr = "val">  	// must be in <attr="val"> format (no space).  	// By default htmlawed rewrites tags to this format.  	// if PHP supported conditional negative lookbehinds we could use this:  	// $r = preg_replace_callback('/(?<!=)(?<![ ])?(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', -	// -	// we can put , in the list of excluded char but need to keep . because of domain names. -	// it is removed in the callback. -	$r = preg_replace_callback('/(?<!=)(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', +	$r = preg_replace_callback('/(?<![=\/"\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\']+)/i',  	create_function(  		'$matches',  		'  			$url = $matches[1]; -			$period = \'\'; -			if (substr($url, -1, 1) == \'.\') { -				$period = \'.\'; -				$url = trim($url, \'.\'); +			$punc = ""; +			$last = substr($url, -1, 1); +			if (in_array($last, array(".", "!", ",", "(", ")"))) { +				$punc = $last; +				$url = rtrim($url, ".!,()");  			}  			$urltext = str_replace("/", "/<wbr />", $url); -			return "<a href=\"$url\">$urltext</a>$period"; +			return "<a href=\"$url\" rel=\"nofollow\">$urltext</a>$punc";  		'  	), $text); @@ -224,7 +229,6 @@ function elgg_normalize_url($url) {  	$php_5_3_0_to_5_3_2 = version_compare(PHP_VERSION, '5.3.0', '>=') &&  			version_compare(PHP_VERSION, '5.3.3', '<'); -	$validated = false;  	if ($php_5_2_13_and_below || $php_5_3_0_to_5_3_2) {  		$tmp_address = str_replace("-", "", $url);  		$validated = filter_var($tmp_address, FILTER_VALIDATE_URL); @@ -285,11 +289,9 @@ function elgg_get_friendly_title($title) {  		return $result;  	} -	// handle some special cases -	$title = str_replace('&', 'and', $title); -	// quotes and angle brackets stored in the database as html encoded -	$title = htmlspecialchars_decode($title); - +	// titles are often stored HTML encoded +	$title = html_entity_decode($title, ENT_QUOTES, 'UTF-8'); +	  	$title = ElggTranslit::urlize($title);  	return $title; @@ -419,9 +421,28 @@ function _elgg_html_decode($string) {  }  /** + * Prepares query string for output to prevent CSRF attacks. + *  + * @param string $string + * @return string + * + * @access private + */ +function _elgg_get_display_query($string) { +	//encode <,>,&, quotes and characters above 127 +	if (function_exists('mb_convert_encoding')) {
 +		$display_query = mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
 +	} else {
 +		// if no mbstring extension, we just strip characters
 +		$display_query = preg_replace("/[^\x01-\x7F]/", "", $string);
 +	}
 +	return htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false); +} + +/**   * Unit tests for Output   * - * @param 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/pageowner.php b/engine/lib/pageowner.php index 94765feee..bd63d08c6 100644 --- a/engine/lib/pageowner.php +++ b/engine/lib/pageowner.php @@ -29,7 +29,9 @@ function elgg_get_page_owner_guid($guid = 0) {  	// return guid of page owner entity  	$guid = elgg_trigger_plugin_hook('page_owner', 'system', NULL, 0); -	$page_owner_guid = $guid; +	if ($guid) { +		$page_owner_guid = $guid; +	}  	return $guid;  } @@ -39,7 +41,7 @@ function elgg_get_page_owner_guid($guid = 0) {   *   * @note Access is disabled when getting the page owner entity.   * - * @return ElggEntity|false The current page owner or false if none. + * @return ElggUser|ElggGroup|false The current page owner or false if none.   *   * @since 1.8.0   */ @@ -113,6 +115,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)  		}  		if ($user = get_user_by_username($username)) { +			elgg_set_ignore_access($ia);  			return $user->getGUID();  		}  	} diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php index 94aff277e..d5d3db466 100644 --- a/engine/lib/plugins.php +++ b/engine/lib/plugins.php @@ -91,7 +91,9 @@ function elgg_get_plugin_ids_in_dir($dir = null) {   * @access private   */  function elgg_generate_plugin_entities() { +	// @todo $site unused, can remove?  	$site = get_config('site'); +  	$dir = elgg_get_plugins_path();  	$db_prefix = elgg_get_config('dbprefix'); @@ -107,6 +109,7 @@ function elgg_generate_plugin_entities() {  	$old_access = access_get_show_hidden_status();  	access_show_hidden_entities(true);  	$known_plugins = elgg_get_entities_from_relationship($options); +	/* @var ElggPlugin[] $known_plugins */  	if (!$known_plugins) {  		$known_plugins = array(); @@ -138,7 +141,7 @@ function elgg_generate_plugin_entities() {  			$index = $id_map[$plugin_id];  			$plugin = $known_plugins[$index];  			// was this plugin deleted and its entity disabled? -			if ($plugin->enabled != 'yes') { +			if (!$plugin->isEnabled()) {  				$plugin->enable();  				$plugin->deactivate();  				$plugin->setPriority('last'); @@ -192,7 +195,7 @@ function _elgg_cache_plugin_by_id(ElggPlugin $plugin) {   * Returns an ElggPlugin object with the path $path.   *   * @param string $plugin_id The id (dir name) of the plugin. NOT the guid. - * @return mixed ElggPlugin or false. + * @return ElggPlugin|false   * @since 1.8.0   */  function elgg_get_plugin_from_id($plugin_id) { @@ -260,6 +263,8 @@ function elgg_get_max_plugin_priority() {  	$data = get_data($q);  	if ($data) {  		$max = $data[0]->max; +	} else { +		$max = 1;  	}  	// can't have a priority of 0. @@ -306,13 +311,11 @@ function elgg_is_active_plugin($plugin_id, $site_guid = null) {   * @access private   */  function elgg_load_plugins() { -	global $CONFIG; -  	$plugins_path = elgg_get_plugins_path(); -	$start_flags =	ELGG_PLUGIN_INCLUDE_START -					| ELGG_PLUGIN_REGISTER_VIEWS -					| ELGG_PLUGIN_REGISTER_LANGUAGES -					| ELGG_PLUGIN_REGISTER_CLASSES; +	$start_flags = ELGG_PLUGIN_INCLUDE_START | +					ELGG_PLUGIN_REGISTER_VIEWS | +					ELGG_PLUGIN_REGISTER_LANGUAGES | +					ELGG_PLUGIN_REGISTER_CLASSES;  	if (!$plugins_path) {  		return false; @@ -360,7 +363,7 @@ function elgg_load_plugins() {   *   * @param string $status      The status of the plugins. active, inactive, or all.   * @param mixed  $site_guid   Optional site guid - * @return array + * @return ElggPlugin[]   * @since 1.8.0   * @access private   */ @@ -441,6 +444,7 @@ function elgg_set_plugin_priorities(array $order) {  	// though we do start with 1  	$order = array_values($order); +	$missing_plugins = array();  	foreach ($plugins as $plugin) {  		$plugin_id = $plugin->getID(); @@ -639,19 +643,18 @@ function elgg_get_plugins_provides($type = null, $name = null) {   * @access private   */  function elgg_check_plugins_provides($type, $name, $version = null, $comparison = 'ge') { -	if (!$provided = elgg_get_plugins_provides($type, $name)) { +	$provided = elgg_get_plugins_provides($type, $name); +	if (!$provided) {  		return array(  			'status' => false,  			'version' => ''  		);  	} -	if ($provided) { -		if ($version) { -			$status = version_compare($provided['version'], $version, $comparison); -		} else { -			$status = true; -		} +	if ($version) { +		$status = version_compare($provided['version'], $version, $comparison); +	} else { +		$status = true;  	}  	return array( @@ -861,9 +864,9 @@ function elgg_set_plugin_user_setting($name, $value, $user_guid = null, $plugin_  /**   * Unsets a user-specific plugin setting   * - * @param str $name      Name of the setting - * @param int $user_guid Defaults to logged in user - * @param str $plugin_id Defaults to contextual plugin name + * @param string $name      Name of the setting + * @param int    $user_guid Defaults to logged in user + * @param string $plugin_id Defaults to contextual plugin name   *   * @return bool   * @since 1.8.0 @@ -1087,7 +1090,7 @@ function plugin_run_once() {  /**   * 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 @@ -1102,6 +1105,49 @@ function plugins_test($hook, $type, $value, $params) {  }  /** + * Checks on deactivate plugin event if disabling it won't create unmet dependencies and blocks disable in such case. + * + * @param string $event  deactivate + * @param string $type   plugin + * @param array  $params Parameters array containing entry with ELggPlugin instance under 'plugin_entity' key + * @return bool  false to block plugin deactivation action + * + * @access private + */ +function _plugins_deactivate_dependency_check($event, $type, $params) { +	$plugin_id = $params['plugin_entity']->getManifest()->getPluginID(); +	$plugin_name = $params['plugin_entity']->getManifest()->getName(); + +	$active_plugins = elgg_get_plugins(); + +	$dependents = array(); +	foreach ($active_plugins as $plugin) { +		$manifest = $plugin->getManifest(); +		$requires = $manifest->getRequires(); + +		foreach ($requires as $required) { +			if ($required['type'] == 'plugin' && $required['name'] == $plugin_id) { +				// there are active dependents +				$dependents[$manifest->getPluginID()] = $plugin; +			} +		} +	} + +	if ($dependents) { +		$list = '<ul>'; +		// construct error message and prevent disabling +		foreach ($dependents as $dependent) { +			$list .= '<li>' . $dependent->getManifest()->getName() . '</li>'; +		} +		$list .= '</ul>'; + +		register_error(elgg_echo('ElggPlugin:Dependencies:ActiveDependent', array($plugin_name, $list))); + +		return false; +	} +} + +/**   * Initialize the plugin system   * Listens to system init and registers actions   * @@ -1112,6 +1158,10 @@ function plugin_init() {  	run_function_once("plugin_run_once");  	elgg_register_plugin_hook_handler('unit_test', 'system', 'plugins_test'); +	 +	// note - plugins are booted by the time this handler is registered +	// deactivation due to error may have already occurred +	elgg_register_event_handler('deactivate', 'plugin', '_plugins_deactivate_dependency_check');  	elgg_register_action("plugins/settings/save", '', 'admin');  	elgg_register_action("plugins/usersettings/save"); diff --git a/engine/lib/relationships.php b/engine/lib/relationships.php index 01654b1ce..b0cd627fc 100644 --- a/engine/lib/relationships.php +++ b/engine/lib/relationships.php @@ -12,7 +12,7 @@   *   * @param stdClass $row Database row from the relationship table   * - * @return stdClass or ElggMetadata + * @return ElggRelationship|stdClass   * @access private   */  function row_to_elggrelationship($row) { @@ -28,7 +28,7 @@ function row_to_elggrelationship($row) {   *   * @param int $id The ID of a relationship   * - * @return mixed + * @return ElggRelationship|false   */  function get_relationship($id) {  	global $CONFIG; @@ -109,7 +109,7 @@ function add_entity_relationship($guid_one, $relationship, $guid_two) {   * @param string $relationship The type of relationship   * @param int    $guid_two     The GUID of the entity the relationship is with   * - * @return object|false Depending on success + * @return ElggRelationship|false Depending on success   */  function check_entity_relationship($guid_one, $relationship, $guid_two) {  	global $CONFIG; @@ -123,7 +123,7 @@ function check_entity_relationship($guid_one, $relationship, $guid_two) {  			AND relationship='$relationship'  			AND guid_two=$guid_two limit 1"; -	$row = get_data_row($query); +	$row = row_to_elggrelationship(get_data_row($query));  	if ($row) {  		return $row;  	} @@ -220,7 +220,7 @@ function remove_entity_relationships($guid_one, $relationship = "", $inverse = f   * @param int  $guid                 The GUID of the relationship owner   * @param bool $inverse_relationship Inverse relationship owners?   * - * @return mixed + * @return ElggRelationship[]   */  function get_entity_relationships($guid, $inverse_relationship = FALSE) {  	global $CONFIG; @@ -259,7 +259,7 @@ function get_entity_relationships($guid, $inverse_relationship = FALSE) {   *   * 	inverse_relationship => BOOL Inverse the relationship   * - * @return mixed If count, int. If not count, array. false on errors. + * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors.   * @since 1.7.0   */  function elgg_get_entities_from_relationship($options) { @@ -316,7 +316,7 @@ function elgg_get_entities_from_relationship($options) {   *                                     Provide in table.column format.   * @param string $relationship         Relationship string   * @param int    $relationship_guid    Entity guid to check - * @param string $inverse_relationship Inverse relationship check? + * @param bool $inverse_relationship Inverse relationship check?   *   * @return mixed   * @since 1.7.0 @@ -363,7 +363,7 @@ $relationship_guid = NULL, $inverse_relationship = FALSE) {  /**   * Returns a viewable list of entities by relationship   * - * @param array $options + * @param array $options Options array for retrieval of entities   *   * @see elgg_list_entities()   * @see elgg_get_entities_from_relationship() @@ -381,7 +381,7 @@ function elgg_list_entities_from_relationship(array $options = array()) {   *   * @param array $options An options array compatible with   *                       elgg_get_entities_from_relationship() - * @return mixed int If count, int. If not count, array. false on errors. + * @return ElggEntity[]|mixed int If count, int. If not count, array. false on errors.   * @since 1.8.0   */  function elgg_get_entities_from_relationship_count(array $options = array()) { @@ -398,7 +398,7 @@ function elgg_get_entities_from_relationship_count(array $options = array()) {   *   * @param array $options Options array   * - * @return array + * @return string   * @since 1.8.0   */  function elgg_list_entities_from_relationship_count($options) { @@ -499,7 +499,7 @@ function already_attached($guid_one, $guid_two) {   * @param int    $guid Entity GUID   * @param string $type The type of object to return e.g. 'file', 'friend_of' etc   * - * @return an array of objects + * @return ElggEntity[]   * @access private   */  function get_attachments($guid, $type = "") { @@ -507,7 +507,7 @@ function get_attachments($guid, $type = "") {  					'relationship' => 'attached',  					'relationship_guid' => $guid,  					'inverse_relationship' => false, -					'types' => $type, +					'type' => $type,  					'subtypes' => '',  					'owner_guid' => 0,  					'order_by' => 'time_created desc', @@ -571,9 +571,8 @@ function import_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par  	if ($element instanceof ODDRelationship) {  		$tmp = new ElggRelationship();  		$tmp->import($element); - -		return $tmp;  	} +	return $tmp;  }  /** @@ -586,11 +585,10 @@ function import_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par   *   * @elgg_event_handler export all   * @return mixed + * @throws InvalidParameterException   * @access private   */  function export_relationship_plugin_hook($hook, $entity_type, $returnvalue, $params) { -	global $CONFIG; -  	// Sanity check values  	if ((!is_array($params)) && (!isset($params['guid']))) {  		throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); @@ -624,9 +622,9 @@ function export_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par   * @access private   */  function relationship_notification_hook($event, $type, $object) { - +	/* @var ElggRelationship $object */  	$user_one = get_entity($object->guid_one); -	$user_two = get_entity($object->guid_two); +	/* @var ElggUser $user_one */  	return notify_user($object->guid_two,  			$object->guid_one, diff --git a/engine/lib/river.php b/engine/lib/river.php index 33f34360e..e92040eb7 100644 --- a/engine/lib/river.php +++ b/engine/lib/river.php @@ -120,7 +120,7 @@ $posted = 0, $annotation_id = 0) {   *   subtypes             => STR|ARR Entity subtype string(s)   *   type_subtype_pairs   => ARR     Array of type => subtype pairs where subtype   *                                   can be an array of subtype strings - *  + *   *   posted_time_lower    => INT     The lower bound on the time posted   *   posted_time_upper    => INT     The upper bound on the time posted   * @@ -380,10 +380,10 @@ function _elgg_prefetch_river_entities(array $river_items) {  	// prefetch objects and subjects  	$guids = array();  	foreach ($river_items as $item) { -		if ($item->subject_guid && !retrieve_cached_entity($item->subject_guid)) { +		if ($item->subject_guid && !_elgg_retrieve_cached_entity($item->subject_guid)) {  			$guids[$item->subject_guid] = true;  		} -		if ($item->object_guid && !retrieve_cached_entity($item->object_guid)) { +		if ($item->object_guid && !_elgg_retrieve_cached_entity($item->object_guid)) {  			$guids[$item->object_guid] = true;  		}  	} @@ -402,7 +402,7 @@ function _elgg_prefetch_river_entities(array $river_items) {  	$guids = array();  	foreach ($river_items as $item) {  		$object = $item->getObjectEntity(); -		if ($object->container_guid && !retrieve_cached_entity($object->container_guid)) { +		if ($object->container_guid && !_elgg_retrieve_cached_entity($object->container_guid)) {  			$guids[$object->container_guid] = true;  		}  	} @@ -434,8 +434,13 @@ function elgg_list_river(array $options = array()) {  		'pagination' => TRUE,  		'list_class' => 'elgg-list-river elgg-river', // @todo remove elgg-river in Elgg 1.9  	); - +	  	$options = array_merge($defaults, $options); +	 +	if (!$options["limit"] && !$options["offset"]) {
 +		// no need for pagination if listing is unlimited
 +		$options["pagination"] = false;
 +	}  	$options['count'] = TRUE;  	$count = elgg_get_river($options); @@ -445,6 +450,7 @@ function elgg_list_river(array $options = array()) {  	$options['count'] = $count;  	$options['items'] = $items; +	  	return elgg_view('page/components/list', $options);  } @@ -500,6 +506,7 @@ function elgg_get_river_type_subtype_where_sql($table, $types, $subtypes, $pairs  		return '';  	} +	$wheres = array();  	$types_wheres = array();  	$subtypes_wheres = array(); @@ -644,7 +651,7 @@ function update_river_access_by_object($object_guid, $access_id) {  }  /** - * Page handler for activiy + * Page handler for activity   *   * @param array $page   * @return bool @@ -663,10 +670,6 @@ function elgg_river_page_handler($page) {  	}  	set_input('page_type', $page_type); -	// content filter code here -	$entity_type = ''; -	$entity_subtype = ''; -  	require_once("{$CONFIG->path}pages/river.php");  	return true;  } diff --git a/engine/lib/sessions.php b/engine/lib/sessions.php index 72ca0a1c2..e3d5ce9cd 100644 --- a/engine/lib/sessions.php +++ b/engine/lib/sessions.php @@ -87,6 +87,9 @@ function elgg_is_admin_logged_in() {   */  function elgg_is_admin_user($user_guid) {  	global $CONFIG; + +	$user_guid = (int)$user_guid; +  	// cannot use magic metadata here because of recursion  	// must support the old way of getting admin from metadata @@ -286,8 +289,6 @@ function check_rate_limit_exceeded($user_guid) {   * @throws LoginException   */  function login(ElggUser $user, $persistent = false) { -	global $CONFIG; -  	// User is banned, return false.  	if ($user->isBanned()) {  		throw new LoginException(elgg_echo('LoginException:BannedUser')); @@ -325,6 +326,12 @@ function login(ElggUser $user, $persistent = false) {  	set_last_login($_SESSION['guid']);  	reset_login_failure_count($user->guid); // Reset any previous failed login attempts +	// if memcache is enabled, invalidate the user in memcache @see https://github.com/Elgg/Elgg/issues/3143 +	if (is_memcache_available()) { +		// this needs to happen with a shutdown function because of the timing with set_last_login() +		register_shutdown_function("_elgg_invalidate_memcache_for_entity", $_SESSION['guid']); +	} +	  	return true;  } @@ -334,8 +341,6 @@ function login(ElggUser $user, $persistent = false) {   * @return bool   */  function logout() { -	global $CONFIG; -  	if (isset($_SESSION['user'])) {  		if (!elgg_trigger_event('logout', 'user', $_SESSION['user'])) {  			return false; @@ -616,10 +621,8 @@ function _elgg_session_destroy($id) {  		global $sess_save_path;  		$sess_file = "$sess_save_path/sess_$id"; -		return(@unlink($sess_file)); +		return @unlink($sess_file);  	} - -	return false;  }  /** diff --git a/engine/lib/sites.php b/engine/lib/sites.php index d9eb2d25e..3de0eccc2 100644 --- a/engine/lib/sites.php +++ b/engine/lib/sites.php @@ -26,7 +26,7 @@ function elgg_get_site_entity($site_guid = 0) {  		$site = get_entity($site_guid);  	} -	if($site instanceof ElggSite){ +	if ($site instanceof ElggSite) {  		$result = $site;  	} @@ -118,8 +118,6 @@ function create_site_entity($guid, $name, $description, $url) {   * @return bool   */  function add_site_user($site_guid, $user_guid) { -	global $CONFIG; -  	$site_guid = (int)$site_guid;  	$user_guid = (int)$user_guid; @@ -150,8 +148,6 @@ function remove_site_user($site_guid, $user_guid) {   * @return mixed   */  function add_site_object($site_guid, $object_guid) { -	global $CONFIG; -  	$site_guid = (int)$site_guid;  	$object_guid = (int)$object_guid; @@ -192,8 +188,8 @@ function get_site_objects($site_guid, $subtype = "", $limit = 10, $offset = 0) {  		'relationship' => 'member_of_site',  		'relationship_guid' => $site_guid,  		'inverse_relationship' => TRUE, -		'types' => 'object', -		'subtypes' => $subtype, +		'type' => 'object', +		'subtype' => $subtype,  		'limit' => $limit,  		'offset' => $offset  	)); @@ -242,7 +238,7 @@ function get_site_domain($guid) {  /**   * Unit tests for sites   * - * @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/statistics.php b/engine/lib/statistics.php index 5ee640549..4cb0bb0b8 100644 --- a/engine/lib/statistics.php +++ b/engine/lib/statistics.php @@ -95,15 +95,20 @@ function get_number_users($show_deactivated = false) {   * @return string    */  function get_online_users() { -	$count = find_active_users(600, 10, 0, true); -	$objects = find_active_users(600, 10); +	$limit = max(0, (int) get_input("limit", 10)); +	$offset = max(0, (int) get_input("offset", 0)); +	 +	$count = find_active_users(600, $limit, $offset, true); +	$objects = find_active_users(600, $limit, $offset);  	if ($objects) {  		return elgg_view_entity_list($objects, array(  			'count' => $count, -			'limit' => 10 +			'limit' => $limit, +			'offset' => $offset  		));  	} +	return '';  }  /** diff --git a/engine/lib/system_log.php b/engine/lib/system_log.php index 53fa24557..84302632e 100644 --- a/engine/lib/system_log.php +++ b/engine/lib/system_log.php @@ -10,6 +10,8 @@  /**   * Retrieve the system log based on a number of parameters.   * + * @todo too many args, and the first arg is too confusing + *   * @param int|array $by_user    The guid(s) of the user(s) who initiated the event.   *                              Use 0 for unowned entries. Anything else falsey means anyone.   * @param string    $event      The event you are searching on. @@ -22,12 +24,12 @@   * @param int       $timebefore Lower time limit   * @param int       $timeafter  Upper time limit   * @param int       $object_id  GUID of an object - * @param str       $ip_address The IP address. + * @param string    $ip_address The IP address.   * @return mixed   */ -function get_system_log($by_user = "", $event = "", $class = "", $type = "", $subtype = "", -$limit = 10, $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $object_id = 0, -$ip_address = false) { +function get_system_log($by_user = "", $event = "", $class = "", $type = "", $subtype = "", $limit = 10, +						$offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $object_id = 0, +						$ip_address = "") {  	global $CONFIG; @@ -166,6 +168,7 @@ function system_log($object, $event) {  	if ($object instanceof Loggable) { +		/* @var ElggEntity|ElggExtender $object */  		if (datalist_get('version') < 2012012000) {  			// this is a site that doesn't have the ip_address column yet  			return; @@ -184,7 +187,16 @@ function system_log($object, $event) {  		$object_subtype = $object->getSubtype();  		$event = sanitise_string($event);  		$time = time(); -		$ip_address = sanitise_string($_SERVER['REMOTE_ADDR']); + +		if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { +			$ip_address = array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])); +		} elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) { +			$ip_address = array_pop(explode(',', $_SERVER['HTTP_X_REAL_IP'])); +		} else { +			$ip_address = $_SERVER['REMOTE_ADDR']; +		} +		$ip_address = sanitise_string($ip_address); +  		$performed_by = elgg_get_logged_in_user_guid();  		if (isset($object->access_id)) { diff --git a/engine/lib/tags.php b/engine/lib/tags.php index a0887d0f3..586a9b9e4 100644 --- a/engine/lib/tags.php +++ b/engine/lib/tags.php @@ -48,7 +48,7 @@ function calculate_tag_size($min, $max, $number_of_tags, $buckets = 6) {   * @param array $tags    The array of tags.   * @param int   $buckets The number of buckets   * - * @return An associated array of tags with a weighting, this can then be mapped to a display class. + * @return array An associated array of tags with a weighting, this can then be mapped to a display class.   * @access private   */  function generate_tag_cloud(array $tags, $buckets = 6) { @@ -114,8 +114,8 @@ function generate_tag_cloud(array $tags, $buckets = 6) {   *   * 	joins => array() Additional joins   * - * @return 	false/array - if no tags or error, false - * 			otherwise, array of objects with ->tag and ->total values + * @return 	object[]|false If no tags or error, false + * 						   otherwise, array of objects with ->tag and ->total values   * @since 1.7.1   */  function elgg_get_tags(array $options = array()) { @@ -172,6 +172,7 @@ function elgg_get_tags(array $options = array()) {  	// catch for tags that were spaces  	$wheres[] = "msv.string != ''"; +	$sanitised_tags = array();  	foreach ($options['tag_names'] as $tag) {  		$sanitised_tags[] = '"' . sanitise_string($tag) . '"';  	} diff --git a/engine/lib/upgrade.php b/engine/lib/upgrade.php index f4f4b16f5..158ec9ec1 100644 --- a/engine/lib/upgrade.php +++ b/engine/lib/upgrade.php @@ -17,8 +17,9 @@   * @access private   */  function upgrade_code($version, $quiet = FALSE) { +	// do not remove - upgrade scripts depend on this  	global $CONFIG; - +	  	$version = (int) $version;  	$upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/';  	$processed_upgrades = elgg_get_processed_upgrades(); @@ -244,7 +245,7 @@ function version_upgrade() {  	// No version number? Oh snap...this is an upgrade from a clean installation < 1.7.  	// Run all upgrades without error reporting and hope for the best. -	// See http://trac.elgg.org/elgg/ticket/1432 for more. +	// See https://github.com/elgg/elgg/issues/1432 for more.  	$quiet = !$dbversion;  	// Note: Database upgrades are deprecated as of 1.8.  Use code upgrades.  See #1433 @@ -291,7 +292,6 @@ function elgg_upgrade_bootstrap_17_to_18() {  		'2011010101.php',  	); -	$upgrades_17 = array();  	$upgrade_files = elgg_get_upgrade_files();  	$processed_upgrades = array(); @@ -354,15 +354,12 @@ function _elgg_upgrade_unlock() {   * @access private   */  function _elgg_upgrade_is_locked() { -	global $CONFIG, $DB_QUERY_CACHE; -	 +	global $CONFIG; +  	$is_locked = count(get_data("show tables like '{$CONFIG->dbprefix}upgrade_lock'")); -	 -	// Invalidate query cache -	if ($DB_QUERY_CACHE) { -		$DB_QUERY_CACHE->clear(); -		elgg_log("Query cache invalidated", 'NOTICE'); -	} -	 + +	// @todo why? +	_elgg_invalidate_query_cache(); +  	return $is_locked;  } diff --git a/engine/lib/upgrades/2009102801.php b/engine/lib/upgrades/2009102801.php index cab9a6835..3ad113fb2 100644 --- a/engine/lib/upgrades/2009102801.php +++ b/engine/lib/upgrades/2009102801.php @@ -203,14 +203,15 @@ function user_file_matrix($guid) {  	return "$time_created/$user->guid/";  } -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE; +global $ENTITY_CACHE, $CONFIG;  /**   * Upgrade file locations   */  $users = mysql_query("SELECT guid, username  	FROM {$CONFIG->dbprefix}users_entity WHERE username != ''");  while ($user = mysql_fetch_object($users)) { -	$DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); +	$ENTITY_CACHE = array(); +	_elgg_invalidate_query_cache();  	$to = $CONFIG->dataroot . user_file_matrix($user->guid);  	foreach (array('1_0', '1_1', '1_6') as $version) { diff --git a/engine/lib/upgrades/2010033101.php b/engine/lib/upgrades/2010033101.php index 0bffee001..4779295fd 100644 --- a/engine/lib/upgrades/2010033101.php +++ b/engine/lib/upgrades/2010033101.php @@ -1,7 +1,7 @@  <?php  /** - * Conditional upgrade for UTF8 as described in http://trac.elgg.org/ticket/1928 + * Conditional upgrade for UTF8 as described in https://github.com/elgg/elgg/issues/1928   */  // get_version() returns the code version. diff --git a/engine/lib/upgrades/2010061501.php b/engine/lib/upgrades/2010061501.php index 9ff7d3102..744c28fd5 100644 --- a/engine/lib/upgrades/2010061501.php +++ b/engine/lib/upgrades/2010061501.php @@ -45,7 +45,7 @@ if ($dbversion < 2009100701) {  			}  		} -		global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE; +		global $ENTITY_CACHE;  		/**  			Upgrade file locations @@ -60,7 +60,9 @@ if ($dbversion < 2009100701) {  		$users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity  			WHERE username != ''", $link);  		while ($user = mysql_fetch_object($users)) { -			$DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); +			$ENTITY_CACHE = array(); +			_elgg_invalidate_query_cache(); +  			$to = $CONFIG->dataroot . user_file_matrix($user->guid);  			foreach (array('1_0', '1_1', '1_6') as $version) { diff --git a/engine/lib/upgrades/2010071001.php b/engine/lib/upgrades/2010071001.php index 1b5d379d8..5594493a8 100644 --- a/engine/lib/upgrades/2010071001.php +++ b/engine/lib/upgrades/2010071001.php @@ -30,11 +30,12 @@ function user_file_matrix_2010071001($guid) {  $sizes = array('large', 'medium', 'small', 'tiny', 'master', 'topbar'); -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +global $ENTITY_CACHE, $CONFIG;  $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity  	WHERE username != ''");  while ($user = mysql_fetch_object($users)) { -	$DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); +	$ENTITY_CACHE = array(); +	_elgg_invalidate_query_cache();  	$user_directory = user_file_matrix_2010071001($user->guid);  	if (!$user_directory) { diff --git a/engine/lib/upgrades/2010071002.php b/engine/lib/upgrades/2010071002.php index 30bd6538c..52aa15ef5 100644 --- a/engine/lib/upgrades/2010071002.php +++ b/engine/lib/upgrades/2010071002.php @@ -4,12 +4,13 @@   */  // loop through all users checking collections and notifications -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +global $ENTITY_CACHE, $CONFIG;  global $NOTIFICATION_HANDLERS;  $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity  	WHERE username != ''");  while ($user = mysql_fetch_object($users)) { -	$DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); +	$ENTITY_CACHE = array(); +	_elgg_invalidate_query_cache();  	$user = get_entity($user->guid);  	foreach ($NOTIFICATION_HANDLERS as $method => $foo) { diff --git a/engine/lib/upgrades/2011052801.php b/engine/lib/upgrades/2011052801.php index 8084bc06c..b5a8e1018 100644 --- a/engine/lib/upgrades/2011052801.php +++ b/engine/lib/upgrades/2011052801.php @@ -2,7 +2,7 @@  /**   * Make sure all users have the relationship member_of_site   */ -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +global $ENTITY_CACHE;  $db_prefix = get_config('dbprefix');  $limit = 100; @@ -17,7 +17,8 @@ $q = "SELECT e.* FROM {$db_prefix}entities e  $users = get_data($q);  while ($users) { -	$DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); +	$ENTITY_CACHE = array(); +	_elgg_invalidate_query_cache();  	// do manually to not trigger any events because these aren't new users.  	foreach ($users as $user) { diff --git a/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php index 07732f261..780038c32 100644 --- a/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php +++ b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php @@ -3,7 +3,7 @@   * Elgg 1.8.3 upgrade 2012041801   * multiple_user_tokens   * - * Fixes http://trac.elgg.org/ticket/4291 + * Fixes https://github.com/elgg/elgg/issues/4291   * Removes the unique index on users_apisessions for user_guid and site_guid   */ diff --git a/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php new file mode 100644 index 000000000..8eccf05e2 --- /dev/null +++ b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php @@ -0,0 +1,24 @@ +<?php +/** + * Elgg 1.8.14 upgrade 2013030600 + * update_user_location + * + * Before Elgg 1.8, a location like "London, England" would be stored as an array. + * This script turns that back into a string. + */ + +$ia = elgg_set_ignore_access(true); +$options = array( +	'type' => 'user', +	'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { +	_elgg_invalidate_query_cache(); +	 +	if (is_array($entity->location)) { +		$entity->location = implode(', ', $entity->location); +	} +} +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php new file mode 100644 index 000000000..ee99bdbc8 --- /dev/null +++ b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php @@ -0,0 +1,28 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013051700 + * add_missing_group_index + * + * Some Elgg sites are missing the groups_entity full text index on name and + * description. This checks if it exists and adds it if it does not. + */ + +$db_prefix = elgg_get_config('dbprefix'); + +$full_text_index_exists = false; +$results = get_data("SHOW INDEX FROM {$db_prefix}groups_entity"); +if ($results) { +	foreach ($results as $result) { +		if ($result->Index_type === 'FULLTEXT') { +			$full_text_index_exists = true; +		} +	} +} + +if ($full_text_index_exists == false) { +	$query = "ALTER TABLE {$db_prefix}groups_entity  +		ADD FULLTEXT name_2 (name, description)"; +	if (!update_data($query)) { +		elgg_log("Failed to add full text index to groups_entity table", 'ERROR'); +	} +} diff --git a/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php new file mode 100644 index 000000000..d333a6cd2 --- /dev/null +++ b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013052900 + * ipv6_in_syslog + * + * Upgrade the ip column in system_log to be able to store ipv6 addresses + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}system_log MODIFY COLUMN ip_address varchar(46) NOT NULL"; + +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php new file mode 100644 index 000000000..538d74dd6 --- /dev/null +++ b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php @@ -0,0 +1,16 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013060900 + * site_secret + * + * Description + */ + +$strength = _elgg_get_site_secret_strength(); + +if ($strength !== 'strong') { +	// a new key is needed immediately +	register_translations(elgg_get_root_path() . 'languages/'); + +	elgg_add_admin_notice('weak_site_key', elgg_echo("upgrade:site_secret_warning:$strength")); +} diff --git a/engine/lib/upgrades/create_upgrade.php b/engine/lib/upgrades/create_upgrade.php index 3652e18a2..b34f31b7e 100644 --- a/engine/lib/upgrades/create_upgrade.php +++ b/engine/lib/upgrades/create_upgrade.php @@ -93,7 +93,7 @@ if (!$h) {  	die("Could not open file $upgrade_file");  } -if (!fputs($h, $upgrade_code)) { +if (!fwrite($h, $upgrade_code)) {  	die("Could not write to $upgrade_file");  } else {  	elgg_set_version_dot_php_version($upgrade_version); @@ -128,8 +128,9 @@ function elgg_set_version_dot_php_version($version) {  	rewind($h); -	fputs($h, $out); +	fwrite($h, $out);  	fclose($h); +	return true;  }  /** diff --git a/engine/lib/user_settings.php b/engine/lib/user_settings.php index e4069fb53..0e36dc46d 100644 --- a/engine/lib/user_settings.php +++ b/engine/lib/user_settings.php @@ -265,9 +265,9 @@ function elgg_set_user_default_access() {   * @access private   */  function usersettings_pagesetup() { -	if (elgg_get_context() == "settings") { -		$user = elgg_get_page_owner_entity(); +	$user = elgg_get_page_owner_entity(); +	if ($user && elgg_get_context() == "settings") {  		$params = array(  			'name' => '1_account',  			'text' => elgg_echo('usersettings:user:opt:linktext'), @@ -308,7 +308,7 @@ function usersettings_page_handler($page) {  		$user = get_user_by_username($page[1]);  		elgg_set_page_owner_guid($user->guid);  	} else { -		$user = elgg_get_logged_in_user_guid(); +		$user = elgg_get_logged_in_user_entity();  		elgg_set_page_owner_guid($user->guid);  	} @@ -332,6 +332,7 @@ function usersettings_page_handler($page) {  		require $path;  		return true;  	} +	return false;  }  /** diff --git a/engine/lib/users.php b/engine/lib/users.php index 95ef9d176..a8fb9121c 100644 --- a/engine/lib/users.php +++ b/engine/lib/users.php @@ -237,7 +237,7 @@ function make_user_admin($user_guid) {  			}  			$r = update_data("UPDATE {$CONFIG->dbprefix}users_entity set admin='yes' where guid=$user_guid"); -			invalidate_cache_for_entity($user_guid); +			_elgg_invalidate_cache_for_entity($user_guid);  			return $r;  		} @@ -273,7 +273,7 @@ function remove_user_admin($user_guid) {  			}  			$r = update_data("UPDATE {$CONFIG->dbprefix}users_entity set admin='no' where guid=$user_guid"); -			invalidate_cache_for_entity($user_guid); +			_elgg_invalidate_cache_for_entity($user_guid);  			return $r;  		} @@ -290,7 +290,7 @@ function remove_user_admin($user_guid) {   * @param int $limit     Number of results to return   * @param int $offset    Any indexing offset   * - * @return false|array On success, an array of ElggSites + * @return ElggSite[]|false On success, an array of ElggSites   */  function get_user_sites($user_guid, $limit = 10, $offset = 0) {  	$user_guid = (int)$user_guid; @@ -302,7 +302,7 @@ function get_user_sites($user_guid, $limit = 10, $offset = 0) {  		'relationship' => 'member_of_site',  		'relationship_guid' => $user_guid,  		'inverse_relationship' => FALSE, -		'types' => 'site', +		'type' => 'site',  		'limit' => $limit,  		'offset' => $offset,  	)); @@ -343,8 +343,6 @@ function user_add_friend($user_guid, $friend_guid) {   * @return bool Depending on success   */  function user_remove_friend($user_guid, $friend_guid) { -	global $CONFIG; -  	$user_guid = (int) $user_guid;  	$friend_guid = (int) $friend_guid; @@ -379,7 +377,7 @@ function user_is_friend($user_guid, $friend_guid) {   * @param int    $limit     Number of results to return (default 10)   * @param int    $offset    Indexing offset, if any   * - * @return false|array Either an array of ElggUsers or false, depending on success + * @return ElggUser[]|false Either an array of ElggUsers or false, depending on success   */  function get_user_friends($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10,  $offset = 0) { @@ -387,8 +385,8 @@ $offset = 0) {  	return elgg_get_entities_from_relationship(array(  		'relationship' => 'friend',  		'relationship_guid' => $user_guid, -		'types' => 'user', -		'subtypes' => $subtype, +		'type' => 'user', +		'subtype' => $subtype,  		'limit' => $limit,  		'offset' => $offset  	)); @@ -402,7 +400,7 @@ $offset = 0) {   * @param int    $limit     Number of results to return (default 10)   * @param int    $offset    Indexing offset, if any   * - * @return false|array Either an array of ElggUsers or false, depending on success + * @return ElggUser[]|false Either an array of ElggUsers or false, depending on success   */  function get_user_friends_of($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10,  $offset = 0) { @@ -411,8 +409,8 @@ $offset = 0) {  		'relationship' => 'friend',  		'relationship_guid' => $user_guid,  		'inverse_relationship' => TRUE, -		'types' => 'user', -		'subtypes' => $subtype, +		'type' => 'user', +		'subtype' => $subtype,  		'limit' => $limit,  		'offset' => $offset  	)); @@ -428,7 +426,7 @@ $offset = 0) {   * @param int    $timelower The earliest time the entity can have been created. Default: all   * @param int    $timeupper The latest time the entity can have been created. Default: all   * - * @return false|array An array of ElggObjects or false, depending on success + * @return ElggObject[]|false An array of ElggObjects or false, depending on success   */  function get_user_friends_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10,  $offset = 0, $timelower = 0, $timeupper = 0) { @@ -555,13 +553,18 @@ function get_user($guid) {  function get_user_by_username($username) {  	global $CONFIG, $USERNAME_TO_GUID_MAP_CACHE; +	// Fixes #6052. Username is frequently sniffed from the path info, which, +	// unlike $_GET, is not URL decoded. If the username was not URL encoded, +	// this is harmless. +	$username = rawurldecode($username); +  	$username = sanitise_string($username);  	$access = get_access_sql_suffix('e');  	// Caching  	if ((isset($USERNAME_TO_GUID_MAP_CACHE[$username])) -	&& (retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]))) { -		return retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]); +			&& (_elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]))) { +		return _elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]);  	}  	$query = "SELECT e.* from {$CONFIG->dbprefix}users_entity u @@ -594,9 +597,9 @@ function get_user_by_code($code) {  	// Caching  	if ((isset($CODE_TO_GUID_MAP_CACHE[$code])) -	&& (retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]))) { +	&& (_elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]))) { -		return retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]); +		return _elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]);  	}  	$query = "SELECT e.* from {$CONFIG->dbprefix}users_entity u @@ -675,25 +678,22 @@ function find_active_users($seconds = 600, $limit = 10, $offset = 0, $count = fa   * @return bool   */  function send_new_password_request($user_guid) { -	global $CONFIG; -  	$user_guid = (int)$user_guid;  	$user = get_entity($user_guid); -	if ($user) { +	if ($user instanceof ElggUser) {  		// generate code  		$code = generate_random_cleartext_password();  		$user->setPrivateSetting('passwd_conf_code', $code); -  		// generate link -		$link = $CONFIG->site->url . "resetpassword?u=$user_guid&c=$code"; +		$link = elgg_get_site_url() . "resetpassword?u=$user_guid&c=$code";  		// generate email  		$email = elgg_echo('email:resetreq:body', array($user->name, $_SERVER['REMOTE_ADDR'], $link)); -		return notify_user($user->guid, $CONFIG->site->guid, -			elgg_echo('email:resetreq:subject'), $email, NULL, 'email'); +		return notify_user($user->guid, elgg_get_site_entity()->guid, +			elgg_echo('email:resetreq:subject'), $email, array(), 'email');  	}  	return false; @@ -710,19 +710,18 @@ function send_new_password_request($user_guid) {   * @return bool   */  function force_user_password_reset($user_guid, $password) { -	global $CONFIG; -  	$user = get_entity($user_guid); +	if ($user instanceof ElggUser) { +		$ia = elgg_set_ignore_access(); -	if ($user) { -		$salt = generate_random_cleartext_password(); // Reset the salt -		$user->salt = $salt; +		$user->salt = generate_random_cleartext_password(); +		$hash = generate_user_password($user, $password);		 +		$user->password = $hash; +		$result = (bool)$user->save(); -		$hash = generate_user_password($user, $password); +		elgg_set_ignore_access($ia); -		$query = "UPDATE {$CONFIG->dbprefix}users_entity -			set password='$hash', salt='$salt' where guid=$user_guid"; -		return update_data($query); +		return $result;  	}  	return false; @@ -742,7 +741,7 @@ function execute_new_password_request($user_guid, $conf_code) {  	$user_guid = (int)$user_guid;  	$user = get_entity($user_guid); -	if ($user) { +	if ($user instanceof ElggUser) {  		$saved_code = $user->getPrivateSetting('passwd_conf_code');  		if ($saved_code && $saved_code == $conf_code) { @@ -756,7 +755,7 @@ function execute_new_password_request($user_guid, $conf_code) {  				$email = elgg_echo('email:resetpassword:body', array($user->name, $password));  				return notify_user($user->guid, $CONFIG->site->guid, -					elgg_echo('email:resetpassword:subject'), $email, NULL, 'email'); +					elgg_echo('email:resetpassword:subject'), $email, array(), 'email');  			}  		}  	} @@ -841,7 +840,7 @@ function validate_username($username) {  	for ($n = 0; $n < strlen($blacklist2); $n++) {  		if (strpos($username, $blacklist2[$n]) !== false) {  			$msg = elgg_echo('registration:invalidchars', array($blacklist2[$n], $blacklist2)); -			$msg = htmlentities($msg, ENT_COMPAT, 'UTF-8'); +			$msg = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8');  			throw new RegistrationException($msg);  		}  	} @@ -908,13 +907,11 @@ function validate_email_address($address) {   * @param string $invitecode            An invite code from a friend   *   * @return int|false The new user's GUID; false on failure + * @throws RegistrationException   */  function register_user($username, $password, $name, $email,  $allow_multiple_emails = false, $friend_guid = 0, $invitecode = '') { -	// Load the configuration -	global $CONFIG; -  	// no need to trim password.  	$username = trim($username);  	$name = trim(strip_tags($name)); @@ -1031,7 +1028,7 @@ function elgg_get_user_validation_status($user_guid) {  		'metadata_name' => 'validated'  	));  	if ($md == false) { -		return; +		return null;  	}  	if ($md[0]->value) { @@ -1067,10 +1064,10 @@ function collections_submenu_items() {   * @return bool   * @access private   */ -function friends_page_handler($page_elements, $handler) { +function friends_page_handler($segments, $handler) {  	elgg_set_context('friends'); -	if (isset($page_elements[0]) && $user = get_user_by_username($page_elements[0])) { +	if (isset($segments[0]) && $user = get_user_by_username($segments[0])) {  		elgg_set_page_owner_guid($user->getGUID());  	}  	if (elgg_get_logged_in_user_guid() == elgg_get_page_owner_guid()) { @@ -1099,6 +1096,7 @@ function friends_page_handler($page_elements, $handler) {   * @access private   */  function collections_page_handler($page_elements) { +	gatekeeper();  	elgg_set_context('friends');  	$base = elgg_get_config('path');  	if (isset($page_elements[0])) { @@ -1197,13 +1195,11 @@ function set_last_login($user_guid) {   * @param string   $object_type user   * @param ElggUser $object      User object   * - * @return bool + * @return void   * @access private   */  function user_create_hook_add_site_relationship($event, $object_type, $object) { -	global $CONFIG; - -	add_entity_relationship($object->getGUID(), 'member_of_site', $CONFIG->site->getGUID()); +	add_entity_relationship($object->getGUID(), 'member_of_site', elgg_get_site_entity()->guid);  }  /** @@ -1233,6 +1229,7 @@ function user_avatar_hook($hook, $entity_type, $returnvalue, $params) {   */  function elgg_user_hover_menu($hook, $type, $return, $params) {  	$user = $params['entity']; +	/* @var ElggUser $user */  	if (elgg_is_logged_in()) {  		if (elgg_get_logged_in_user_guid() != $user->guid) { @@ -1309,7 +1306,12 @@ function elgg_user_hover_menu($hook, $type, $return, $params) {  /**   * Setup the menu shown with an entity   * + * @param string $hook + * @param string $type + * @param array $return + * @param array $params   * @return array + *   * @access private   */  function elgg_users_setup_entity_menu($hook, $type, $return, $params) { @@ -1321,6 +1323,7 @@ function elgg_users_setup_entity_menu($hook, $type, $return, $params) {  	if (!elgg_instanceof($entity, 'user')) {  		return $return;  	} +	/* @var ElggUser $entity */  	if ($entity->isBanned()) {  		$banned = elgg_echo('banned'); @@ -1334,9 +1337,10 @@ function elgg_users_setup_entity_menu($hook, $type, $return, $params) {  	} else {  		$return = array();  		if (isset($entity->location)) { +			$location = htmlspecialchars($entity->location, ENT_QUOTES, 'UTF-8', false);  			$options = array(  				'name' => 'location', -				'text' => "<span>$entity->location</span>", +				'text' => "<span>$location</span>",  				'href' => false,  				'priority' => 150,  			); @@ -1587,7 +1591,7 @@ function users_init() {  /**   * Runs unit tests for ElggObject   * - * @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/views.php b/engine/lib/views.php index 01291b889..1142461fe 100644 --- a/engine/lib/views.php +++ b/engine/lib/views.php @@ -218,7 +218,7 @@ function elgg_register_ajax_view($view) {  /**   * Unregister a view for ajax calls - *  + *   * @param string $view The view name   * @return void   * @since 1.8.3 @@ -369,7 +369,7 @@ function elgg_view_exists($view, $viewtype = '', $recurse = true) {   * view, $view_name plugin hook.   *   * @warning Any variables in $_SESSION will override passed vars - * upon name collision.  See {@trac #2124}. + * upon name collision.  See https://github.com/Elgg/Elgg/issues/2124   *   * @param string  $view     The name and location of the view to use   * @param array   $vars     Variables to pass to the view. @@ -795,7 +795,7 @@ function elgg_view_menu($menu_name, array $vars = array()) {   *  - bool 'full_view' Whether to show a full or condensed view.   *   * @tip This function can automatically appends annotations to entities if in full - * view and a handler is registered for the entity:annotate.  See {@trac 964} and + * view and a handler is registered for the entity:annotate.  See https://github.com/Elgg/Elgg/issues/964 and   * {@link elgg_view_entity_annotations()}.   *   * @param ElggEntity $entity The entity to display @@ -992,6 +992,11 @@ function elgg_view_annotation(ElggAnnotation $annotation, array $vars = array(),  function elgg_view_entity_list($entities, $vars = array(), $offset = 0, $limit = 10, $full_view = true,  $list_type_toggle = true, $pagination = true) { +	if (!$vars["limit"] && !$vars["offset"]) { +		// no need for pagination if listing is unlimited
 +		$vars["pagination"] = false;
 +	}
 +		  	if (!is_int($offset)) {  		$offset = (int)get_input('offset', 0);  	} @@ -1064,8 +1069,13 @@ function elgg_view_annotation_list($annotations, array $vars = array()) {  		'full_view' => true,  		'offset_key' => 'annoff',  	); - +	  	$vars = array_merge($defaults, $vars); +	 +	if (!$vars["limit"] && !$vars["offset"]) {
 +		// no need for pagination if listing is unlimited
 +		$vars["pagination"] = false;
 +	}  	return elgg_view('page/components/list', $vars);  } @@ -1107,7 +1117,7 @@ function elgg_view_entity_annotations(ElggEntity $entity, $full_view = true) {   * This is a shortcut for {@elgg_view page/elements/title}.   *   * @param string $title The page title - * @param array $vars   View variables (was submenu be displayed? (deprecated)) + * @param array  $vars  View variables (was submenu be displayed? (deprecated))   *   * @return string The HTML (etc)   */ @@ -1179,7 +1189,7 @@ function elgg_view_comments($entity, $add_comment = true, array $vars = array())   *   * @param string $image The icon and other information   * @param string $body  Description content - * @param array $vars   Additional parameters for the view + * @param array  $vars  Additional parameters for the view   *   * @return string   * @since 1.8.0 @@ -1236,15 +1246,17 @@ function elgg_view_river_item($item, array $vars = array()) {  		// subject is disabled or subject/object deleted  		return '';  	} + +	// @todo this needs to be cleaned up  	// Don't hide objects in closed groups that a user can see. -	// see http://trac.elgg.org/ticket/4789 -//	else { -//		// hide based on object's container -//		$visibility = ElggGroupItemVisibility::factory($object->container_guid); -//		if ($visibility->shouldHideItems) { -//			return ''; -//		} -//	} +	// see https://github.com/elgg/elgg/issues/4789 +	//	else { +	//		// hide based on object's container +	//		$visibility = ElggGroupItemVisibility::factory($object->container_guid); +	//		if ($visibility->shouldHideItems) { +	//			return ''; +	//		} +	//	}  	$vars['item'] = $item; @@ -1308,7 +1320,7 @@ function elgg_view_form($action, $form_vars = array(), $body_vars = array()) {  /**   * View an item in a list   * - * @param object $item ElggEntity or ElggAnnotation + * @param ElggEntity|ElggAnnotation $item   * @param array  $vars Additional parameters for the rendering   *   * @return string @@ -1332,12 +1344,12 @@ function elgg_view_list_item($item, array $vars = array()) {  /**   * View one of the elgg sprite icons - *  + *   * Shorthand for <span class="elgg-icon elgg-icon-$name"></span> - *  + *   * @param string $name  The specific icon to display   * @param string $class Additional class: float, float-alt, or custom class - *  + *   * @return string The html for displaying an icon   */  function elgg_view_icon($name, $class = '') { @@ -1451,17 +1463,13 @@ function elgg_get_views($dir, $base) {   */  function elgg_view_tree($view_root, $viewtype = "") {  	global $CONFIG; -	static $treecache; +	static $treecache = array();  	// Get viewtype  	if (!$viewtype) {  		$viewtype = elgg_get_viewtype();  	} -	// Has the treecache been initialised? -	if (!isset($treecache)) { -		$treecache = array(); -	}  	// A little light internal caching  	if (!empty($treecache[$view_root])) {  		return $treecache[$view_root]; @@ -1640,7 +1648,7 @@ function elgg_views_boot() {  	}  	// set default icon sizes - can be overridden in settings.php or with plugin -	if (!$CONFIG->icon_sizes) { +	if (!isset($CONFIG->icon_sizes)) {  		$icon_sizes = array(  			'topbar' => array('w' => 16, 'h' => 16, 'square' => TRUE, 'upscale' => TRUE),  			'tiny' => array('w' => 25, 'h' => 25, 'square' => TRUE, 'upscale' => TRUE), diff --git a/engine/lib/web_services.php b/engine/lib/web_services.php index c8e4a13cc..51cad6f39 100644 --- a/engine/lib/web_services.php +++ b/engine/lib/web_services.php @@ -178,7 +178,7 @@ function authenticate_method($method) {  	// check if user authentication is required  	if ($API_METHODS[$method]["require_user_auth"] == true) {  		if ($user_auth_result == false) { -			throw new APIException($user_pam->getFailureMessage()); +			throw new APIException($user_pam->getFailureMessage(), ErrorResult::$RESULT_FAIL_AUTHTOKEN);  		}  	} @@ -1166,6 +1166,17 @@ function list_all_apis() {   * @access private   */  function auth_gettoken($username, $password) { +	// check if username is an email address
 +	if (is_email_address($username)) {
 +		$users = get_user_by_email($username);
 +			
 +		// check if we have a unique user
 +		if (is_array($users) && (count($users) == 1)) {
 +			$username = $users[0]->username;
 +		}
 +	}
 +	
 +	// validate username and password  	if (true === elgg_authenticate($username, $password)) {  		$token = create_user_token($username);  		if ($token) { @@ -1195,7 +1206,7 @@ $ERRORS = array();   *   * @return void   * @access private - *  + *   * @throws Exception   */  function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { @@ -1267,14 +1278,14 @@ function service_handler($handler, $request) {  	$request = explode('/', $request);  	// after the handler, the first identifier is response format -	// ex) http://example.org/services/api/rest/xml/?method=test -	$reponse_format = array_shift($request); +	// ex) http://example.org/services/api/rest/json/?method=test +	$response_format = array_shift($request);  	// Which view - xml, json, ... -	if ($reponse_format) { -		elgg_set_viewtype($reponse_format); +	if ($response_format && elgg_is_valid_view_type($response_format)) { +		elgg_set_viewtype($response_format);  	} else { -		// default to xml -		elgg_set_viewtype("xml"); +		// default to json +		elgg_set_viewtype("json");  	}  	if (!isset($CONFIG->servicehandler) || empty($handler)) { diff --git a/engine/lib/widgets.php b/engine/lib/widgets.php index d73dd6330..699462a1b 100644 --- a/engine/lib/widgets.php +++ b/engine/lib/widgets.php @@ -336,7 +336,7 @@ function elgg_default_widgets_init() {   *   * @param string $event  The event   * @param string $type   The type of object - * @param object $entity The entity being created + * @param ElggEntity $entity The entity being created   * @return void   * @access private   */ @@ -372,6 +372,7 @@ function elgg_create_default_widgets($event, $type, $entity) {  				);  				$widgets = elgg_get_entities_from_private_settings($options); +				/* @var ElggWidget[] $widgets */  				foreach ($widgets as $widget) {  					// change the container and owner diff --git a/engine/lib/xml.php b/engine/lib/xml.php index ff82d7e8a..497459d83 100644 --- a/engine/lib/xml.php +++ b/engine/lib/xml.php @@ -104,7 +104,7 @@ function serialise_array_to_xml(array $data, $n = 0) {   *   * @param string $xml The XML   * - * @return object + * @return ElggXMLElement   */  function xml_to_object($xml) {  	return new ElggXMLElement($xml); diff --git a/engine/schema/mysql.sql b/engine/schema/mysql.sql index 6c6e9db89..4714b71bb 100644 --- a/engine/schema/mysql.sql +++ b/engine/schema/mysql.sql @@ -361,7 +361,7 @@ CREATE TABLE `prefix_system_log` (    `access_id` int(11) NOT NULL,    `enabled` enum('yes','no') NOT NULL DEFAULT 'yes',    `time_created` int(11) NOT NULL, -  `ip_address` varchar(15) NOT NULL, +  `ip_address` varchar(46) NOT NULL,    PRIMARY KEY (`id`),    KEY `object_id` (`object_id`),    KEY `object_class` (`object_class`), diff --git a/engine/tests/api/access_collections.php b/engine/tests/api/access_collections.php index ebcd7d318..4acfae596 100644 --- a/engine/tests/api/access_collections.php +++ b/engine/tests/api/access_collections.php @@ -54,7 +54,6 @@ class ElggCoreAccessCollectionsTest extends ElggCoreUnitTest {  	}  	public function testCreateGetDeleteACL() { -		global $DB_QUERY_CACHE;  		$acl_name = 'test access collection';  		$acl_id = create_access_collection($acl_name); @@ -67,8 +66,6 @@ class ElggCoreAccessCollectionsTest extends ElggCoreUnitTest {  		$this->assertEqual($acl->id, $acl_id);  		if ($acl) { -			$DB_QUERY_CACHE = array(); -			  			$this->assertEqual($acl->name, $acl_name);  			$result = delete_access_collection($acl_id); diff --git a/engine/tests/api/annotations.php b/engine/tests/api/annotations.php index 947292970..c0b0687cc 100644 --- a/engine/tests/api/annotations.php +++ b/engine/tests/api/annotations.php @@ -65,6 +65,86 @@ class ElggCoreAnnotationAPITest extends ElggCoreUnitTest {  		$annotations = elgg_get_annotations($options);  		$this->assertTrue(empty($annotations)); +		// nothing to delete so null returned +		$this->assertNull(elgg_delete_annotations($options)); + +		$this->assertTrue($e->delete()); +	} + +	public function testElggDisableAnnotations() { +		$e = new ElggObject(); +		$e->save(); + +		for ($i=0; $i<30; $i++) { +			$e->annotate('test_annotation', rand(0,10000)); +		} + +		$options = array( +			'guid' => $e->getGUID(), +			'limit' => 0 +		); + +		$this->assertTrue(elgg_disable_annotations($options)); + +		$annotations = elgg_get_annotations($options); +		$this->assertTrue(empty($annotations)); + +		access_show_hidden_entities(true); +		$annotations = elgg_get_annotations($options); +		$this->assertIdentical(30, count($annotations)); +		access_show_hidden_entities(false); + +		$this->assertTrue($e->delete()); +	} + +	public function testElggEnableAnnotations() { +		$e = new ElggObject(); +		$e->save(); + +		for ($i=0; $i<30; $i++) { +			$e->annotate('test_annotation', rand(0,10000)); +		} + +		$options = array( +			'guid' => $e->getGUID(), +			'limit' => 0 +		); + +		$this->assertTrue(elgg_disable_annotations($options)); + +		// cannot see any annotations so returns null +		$this->assertNull(elgg_enable_annotations($options)); + +		access_show_hidden_entities(true); +		$this->assertTrue(elgg_enable_annotations($options)); +		access_show_hidden_entities(false); + +		$annotations = elgg_get_annotations($options); +		$this->assertIdentical(30, count($annotations)); + +		$this->assertTrue($e->delete()); +	} + +	public function testElggAnnotationExists() { +		$e = new ElggObject(); +		$e->save(); +		$guid = $e->getGUID(); + +		$this->assertFalse(elgg_annotation_exists($guid, 'test_annotation')); + +		$e->annotate('test_annotation', rand(0, 10000)); +		$this->assertTrue(elgg_annotation_exists($guid, 'test_annotation')); +		// this metastring should always exist but an annotation of this name should not +		$this->assertFalse(elgg_annotation_exists($guid, 'email')); + +		$options = array( +			'guid' => $guid, +			'limit' => 0 +		); +		$this->assertTrue(elgg_disable_annotations($options)); +		$this->assertTrue(elgg_annotation_exists($guid, 'test_annotation')); +  		$this->assertTrue($e->delete()); +		$this->assertFalse(elgg_annotation_exists($guid, 'test_annotation'));  	}  } diff --git a/engine/tests/api/entity_getter_functions.php b/engine/tests/api/entity_getter_functions.php index 9db248de9..fef9dc0c5 100644 --- a/engine/tests/api/entity_getter_functions.php +++ b/engine/tests/api/entity_getter_functions.php @@ -2648,7 +2648,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest {  			$name = 'test_annotation_' . rand(0, 9999);  			$values = array();  			$options = array( -				'types' => 'object', +				'type' => 'object',  				'subtypes' => $subtypes,  				'limit' => 5  			); @@ -2687,7 +2687,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest {  			$order = array_keys($values);  			$options = array( -				'types' => 'object', +				'type' => 'object',  				'subtypes' => $subtypes,  				'limit' => 5,  				'annotation_name' => $name, @@ -2729,6 +2729,36 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest {  		}  	} +	public function testElggGetEntitiesFromAnnotationCalculationCount() { +		// add two annotations with a unique name to an entity +		// then count the number of entities with that annotation name + +		$subtypes = $this->getRandomValidSubtypes(array('object'), 1); +		$name = 'test_annotation_' . rand(0, 9999); +		$values = array(); +		$options = array( +			'type' => 'object', +			'subtypes' => $subtypes, +			'limit' => 1 +		); +		$es = elgg_get_entities($options); +		$entity = $es[0]; +		$value = rand(0, 9999); +		$entity->annotate($name, $value); +		$value = rand(0, 9999); +		$entity->annotate($name, $value); + +		$options = array( +			'type' => 'object', +			'subtypes' => $subtypes, +			'annotation_name' => $name, +			'calculation' => 'count', +			'count' => true, +		); +		$count = elgg_get_entities_from_annotation_calculation($options); +		$this->assertEqual(1, $count); +	} +  	public function testElggGetAnnotationsAnnotationNames() {  		$options = array('annotation_names' => array());  		$a_e_map = array(); @@ -2817,4 +2847,38 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest {  		$entities = elgg_get_entities($options);  		$this->assertFalse($entities);  	} + +	public function testEGEEmptySubtypePlurality() { +		$options = array( +			'type' => 'user', +			'subtypes' => '' +		); + +		$entities = elgg_get_entities($options); +		$this->assertTrue(is_array($entities)); + +		$options = array( +			'type' => 'user', +			'subtype' => '' +		); + +		$entities = elgg_get_entities($options); +		$this->assertTrue(is_array($entities)); + +		$options = array( +			'type' => 'user', +			'subtype' => array('') +		); + +		$entities = elgg_get_entities($options); +		$this->assertTrue(is_array($entities)); + +		$options = array( +			'type' => 'user', +			'subtypes' => array('') +		); + +		$entities = elgg_get_entities($options); +		$this->assertTrue(is_array($entities)); +	}  } diff --git a/engine/tests/api/helpers.php b/engine/tests/api/helpers.php index 62e4471e0..414fb4145 100644 --- a/engine/tests/api/helpers.php +++ b/engine/tests/api/helpers.php @@ -519,7 +519,7 @@ class ElggCoreHelpersTest extends ElggCoreUnitTest {  		$this->assertIdentical($elements_sorted_string, $test_elements);  	} -	// see http://trac.elgg.org/ticket/4288 +	// see https://github.com/elgg/elgg/issues/4288  	public function testElggBatchIncOffset() {  		// normal increment  		$options = array( @@ -578,6 +578,107 @@ class ElggCoreHelpersTest extends ElggCoreUnitTest {  		$this->assertEqual(11, $j);  	} +	public function testElggBatchReadHandlesBrokenEntities() { +		$num_test_entities = 8; +		$guids = array(); +		for ($i = $num_test_entities; $i > 0; $i--) { +			$entity = new ElggObject(); +			$entity->type = 'object'; +			$entity->subtype = 'test_5357_subtype'; +			$entity->access_id = ACCESS_PUBLIC; +			$entity->save(); +			$guids[] = $entity->guid; +			_elgg_invalidate_cache_for_entity($entity->guid); +		} + +		// break entities such that the first fetch has one incomplete +		// and the second and third fetches have only incompletes! +		$db_prefix = elgg_get_config('dbprefix'); +		delete_data(" +			DELETE FROM {$db_prefix}objects_entity +			WHERE guid IN ({$guids[1]}, {$guids[2]}, {$guids[3]}, {$guids[4]}, {$guids[5]}) +		"); + +		$options = array( +			'type' => 'object', +			'subtype' => 'test_5357_subtype', +			'order_by' => 'e.guid', +		); + +		$entities_visited = array(); +		$batch = new ElggBatch('elgg_get_entities', $options, null, 2); +		/* @var ElggEntity[] $batch */ +		foreach ($batch as $entity) { +			$entities_visited[] = $entity->guid; +		} + +		// The broken entities should not have been visited +		$this->assertEqual($entities_visited, array($guids[0], $guids[6], $guids[7])); + +		// cleanup (including leftovers from previous tests) +		$entity_rows = elgg_get_entities(array_merge($options, array( +			'callback' => '', +			'limit' => false, +		))); +		$guids = array(); +		foreach ($entity_rows as $row) { +			$guids[] = $row->guid; +		} +		delete_data("DELETE FROM {$db_prefix}entities WHERE guid IN (" . implode(',', $guids) . ")"); +		delete_data("DELETE FROM {$db_prefix}objects_entity WHERE guid IN (" . implode(',', $guids) . ")"); +	} + +	public function testElggBatchDeleteHandlesBrokenEntities() { +		$num_test_entities = 8; +		$guids = array(); +		for ($i = $num_test_entities; $i > 0; $i--) { +			$entity = new ElggObject(); +			$entity->type = 'object'; +			$entity->subtype = 'test_5357_subtype'; +			$entity->access_id = ACCESS_PUBLIC; +			$entity->save(); +			$guids[] = $entity->guid; +			_elgg_invalidate_cache_for_entity($entity->guid); +		} + +		// break entities such that the first fetch has one incomplete +		// and the second and third fetches have only incompletes! +		$db_prefix = elgg_get_config('dbprefix'); +		delete_data(" +			DELETE FROM {$db_prefix}objects_entity +			WHERE guid IN ({$guids[1]}, {$guids[2]}, {$guids[3]}, {$guids[4]}, {$guids[5]}) +		"); + +		$options = array( +			'type' => 'object', +			'subtype' => 'test_5357_subtype', +			'order_by' => 'e.guid', +		); + +		$entities_visited = array(); +		$batch = new ElggBatch('elgg_get_entities', $options, null, 2, false); +		/* @var ElggEntity[] $batch */ +		foreach ($batch as $entity) { +			$entities_visited[] = $entity->guid; +			$entity->delete(); +		} + +		// The broken entities should not have been visited +		$this->assertEqual($entities_visited, array($guids[0], $guids[6], $guids[7])); + +		// cleanup (including leftovers from previous tests) +		$entity_rows = elgg_get_entities(array_merge($options, array( +			'callback' => '', +			'limit' => false, +		))); +		$guids = array(); +		foreach ($entity_rows as $row) { +			$guids[] = $row->guid; +		} +		delete_data("DELETE FROM {$db_prefix}entities WHERE guid IN (" . implode(',', $guids) . ")"); +		delete_data("DELETE FROM {$db_prefix}objects_entity WHERE guid IN (" . implode(',', $guids) . ")"); +	} +  	static function elgg_batch_callback_test($options, $reset = false) {  		static $count = 1; diff --git a/engine/tests/api/metadata.php b/engine/tests/api/metadata.php index 825290d80..d23510c6a 100644 --- a/engine/tests/api/metadata.php +++ b/engine/tests/api/metadata.php @@ -123,9 +123,23 @@ class ElggCoreMetadataAPITest extends ElggCoreUnitTest {  		$e->delete();  	} +	/** +	 * https://github.com/Elgg/Elgg/issues/4867 +	 */ +	public function testElggGetEntityMetadataWhereSqlWithFalseValue() { +		$pair = array('name' => 'test' , 'value' => false); +		$result = elgg_get_entity_metadata_where_sql('e', 'metadata', null, null, $pair); +		$where = preg_replace( '/\s+/', ' ', $result['wheres'][0]); +		$this->assertTrue(strpos($where, "msn1.string = 'test' AND BINARY msv1.string = 0") > 0); + +		$result = elgg_get_entity_metadata_where_sql('e', 'metadata', array('test'), array(false)); +		$where = preg_replace( '/\s+/', ' ', $result['wheres'][0]); +		$this->assertTrue(strpos($where, "msn.string IN ('test')) AND ( BINARY msv.string IN ('0')")); +	} +  	// Make sure metadata with multiple values is correctly deleted when re-written  	// by another user -	// http://trac.elgg.org/ticket/2776 +	// https://github.com/elgg/elgg/issues/2776  	public function test_elgg_metadata_multiple_values() {  		$u1 = new ElggUser();  		$u1->username = rand(); diff --git a/engine/tests/api/metadata_cache.php b/engine/tests/api/metadata_cache.php index 846116a7b..7fb328169 100644 --- a/engine/tests/api/metadata_cache.php +++ b/engine/tests/api/metadata_cache.php @@ -166,4 +166,11 @@ class ElggCoreMetadataCacheTest extends ElggCoreUnitTest {  		$actual = $this->cache->filterMetadataHeavyEntities($guids, 6000);  		$this->assertIdentical($actual, $expected);  	} + +	public function testCreateMetadataInvalidates() { +		$this->obj1->foo = 1; +		create_metadata($this->guid1, 'foo', 2, '', elgg_get_logged_in_user_guid(), ACCESS_FRIENDS); + +		$this->assertEqual($this->obj1->foo, 2); +	}  } diff --git a/engine/tests/api/metastrings.php b/engine/tests/api/metastrings.php index 0a8945084..5efdab972 100644 --- a/engine/tests/api/metastrings.php +++ b/engine/tests/api/metastrings.php @@ -55,8 +55,11 @@ class ElggCoreMetastringsTest extends ElggCoreUnitTest {  	 * Called after each test method.  	 */  	public function tearDown() { -		// do not allow SimpleTest to interpret Elgg notices as exceptions -		$this->swallowErrors(); +		access_show_hidden_entities(true); +		elgg_delete_annotations(array( +			'guid' => $this->object->guid, +		)); +		access_show_hidden_entities(false);  	}  	/** @@ -98,6 +101,31 @@ class ElggCoreMetastringsTest extends ElggCoreUnitTest {  		}  	} +	public function testGetMetastringObjectFromIDWithDisabledAnnotation() { +		$name = 'test_annotation_name' . rand(); +		$value = 'test_annotation_value' . rand(); +		$id = create_annotation($this->object->guid, $name, $value); +		$annotation = elgg_get_annotation_from_id($id); +		$this->assertTrue($annotation->disable()); + +		$test = elgg_get_metastring_based_object_from_id($id, 'annotation'); +		$this->assertEqual(false, $test); +	} + +	public function testGetMetastringBasedObjectWithDisabledAnnotation() { +		$name = 'test_annotation_name' . rand(); +		$value = 'test_annotation_value' . rand(); +		$id = create_annotation($this->object->guid, $name, $value); +		$annotation = elgg_get_annotation_from_id($id); +		$this->assertTrue($annotation->disable()); + +		$test = elgg_get_metastring_based_objects(array( +			'metastring_type' => 'annotations', +			'guid' => $this->object->guid, +		)); +		$this->assertEqual(array(), $test); +	} +  	public function testEnableDisableByID() {  		$db_prefix = elgg_get_config('dbprefix');  		$annotations = $this->createAnnotations(1); @@ -119,7 +147,6 @@ class ElggCoreMetastringsTest extends ElggCoreUnitTest {  			// enable  			$ashe = access_get_show_hidden_status();  			access_show_hidden_entities(true); -			flush();  			$this->assertTrue(elgg_set_metastring_based_object_enabled_by_id($id, 'yes', $type));  			$test = get_data($q); diff --git a/engine/tests/api/plugins.php b/engine/tests/api/plugins.php index 114f3991b..d0f111c48 100644 --- a/engine/tests/api/plugins.php +++ b/engine/tests/api/plugins.php @@ -69,7 +69,7 @@ class ElggCorePluginsAPITest extends ElggCoreUnitTest {  			'description' => 'A longer, more interesting description.',  			'website' => 'http://www.elgg.org/',  			'repository' => 'https://github.com/Elgg/Elgg', -			'bugtracker' => 'http://trac.elgg.org', +			'bugtracker' => 'https://github.com/elgg/elgg/issues',  			'donations' => 'http://elgg.org/supporter.php',  			'copyright' => '(C) Elgg Foundation 2011',  			'license' => 'GNU General Public License version 2', @@ -174,7 +174,7 @@ class ElggCorePluginsAPITest extends ElggCoreUnitTest {  	}  		public function testElggPluginManifestGetBugtracker() { -		$this->assertEqual($this->manifest18->getBugTrackerURL(), 'http://trac.elgg.org'); +		$this->assertEqual($this->manifest18->getBugTrackerURL(), 'https://github.com/elgg/elgg/issues');  		$this->assertEqual($this->manifest17->getBugTrackerURL(), '');  	} diff --git a/engine/tests/objects/entities.php b/engine/tests/objects/entities.php index 248b85c9e..bac72079e 100644 --- a/engine/tests/objects/entities.php +++ b/engine/tests/objects/entities.php @@ -271,7 +271,7 @@ class ElggCoreEntityTest extends ElggCoreUnitTest {  		$this->save_entity();  		// test deleting incorrectly -		// @link http://trac.elgg.org/ticket/2273 +		// @link https://github.com/elgg/elgg/issues/2273  		$this->assertNull($this->entity->deleteMetadata('impotent'));  		$this->assertEqual($this->entity->important, 'indeed!'); diff --git a/engine/tests/objects/objects.php b/engine/tests/objects/objects.php index 915594e0a..263ab2414 100644 --- a/engine/tests/objects/objects.php +++ b/engine/tests/objects/objects.php @@ -194,7 +194,7 @@ class ElggCoreObjectTest extends ElggCoreUnitTest {  		$old = elgg_set_ignore_access(true);  	} -	// see http://trac.elgg.org/ticket/1196 +	// see https://github.com/elgg/elgg/issues/1196  	public function testElggEntityRecursiveDisableWhenLoggedOut() {  		$e1 = new ElggObject();  		$e1->access_id = ACCESS_PUBLIC; diff --git a/engine/tests/objects/users.php b/engine/tests/objects/users.php index a3573acb6..8a1033ac4 100644 --- a/engine/tests/objects/users.php +++ b/engine/tests/objects/users.php @@ -65,6 +65,9 @@ class ElggCoreUserTest extends ElggCoreUnitTest {  		$attributes['code'] = NULL;  		$attributes['banned'] = 'no';  		$attributes['admin'] = 'no'; +		$attributes['prev_last_action'] = NULL; +		$attributes['last_login'] = NULL; +		$attributes['prev_last_login'] = NULL;  		ksort($attributes);  		$entity_attributes = $this->user->expose_attributes(); @@ -142,7 +145,7 @@ class ElggCoreUserTest extends ElggCoreUnitTest {  	}  	public function testElggUserNameCache() { -		// Trac #1305 +		// issue https://github.com/elgg/elgg/issues/1305  		// very unlikely a user would have this username  		$name = (string)time(); @@ -156,6 +159,22 @@ class ElggCoreUserTest extends ElggCoreUnitTest {  		$this->assertFalse($user);  	} +	public function testGetUserByUsernameAcceptsUrlEncoded() { +		$username = (string)time(); +		$this->user->username = $username; +		$guid = $this->user->save(); + +		// percent encode first letter +		$first_letter = $username[0]; +		$first_letter = str_pad('%' . dechex(ord($first_letter)), 2, '0', STR_PAD_LEFT); +		$username =   $first_letter . substr($username, 1); + +		$user = get_user_by_username($username); +		$this->assertTrue((bool) $user); +		$this->assertEqual($guid, $user->guid); + +		$this->user->delete(); +	}  	public function testElggUserMakeAdmin() {  		global $CONFIG; diff --git a/engine/tests/regression/trac_bugs.php b/engine/tests/regression/trac_bugs.php index 691433a41..689275661 100644 --- a/engine/tests/regression/trac_bugs.php +++ b/engine/tests/regression/trac_bugs.php @@ -1,7 +1,7 @@  <?php  /** - * Elgg Regression Tests -- Trac Bugfixes - * Any bugfixes from Trac that require testing belong here. + * Elgg Regression Tests -- GitHub Bugfixes + * Any bugfixes from GitHub that require testing belong here.   *   * @package Elgg   * @subpackage Test @@ -201,26 +201,28 @@ class ElggCoreRegressionBugsTest extends ElggCoreUnitTest {  	}  	/** -	 * http://trac.elgg.org/ticket/3210 - Don't remove -s in friendly titles -	 * http://trac.elgg.org/ticket/2276 - improve char encoding +	 * https://github.com/elgg/elgg/issues/3210 - Don't remove -s in friendly titles +	 * https://github.com/elgg/elgg/issues/2276 - improve char encoding  	 */  	public function test_friendly_title() {  		$cases = array( +			// acid test +			"B&N > Amazon, OK? <bold> 'hey!' $34" +			=> "bn-amazon-ok-bold-hey-34", +  			// 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", - +			"a-a_a a\na\ra\ta\va!a\"a#a\$a%aa'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-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", +			  			// separators trimmed -			"-_ hello _-" => "hello", +			"-_ 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 @@ -234,4 +236,170 @@ class ElggCoreRegressionBugsTest extends ElggCoreUnitTest {  			$this->assertIdentical($expected, $friendly_title);  		}  	} + +	/** +	 * Test #5369 -- parse_urls() +	 * https://github.com/Elgg/Elgg/issues/5369 +	 */ +	public function test_parse_urls() { + +		$cases = array( +			'no.link.here' => +				'no.link.here', +			'simple link http://example.org test' => +				'simple link <a href="http://example.org" rel="nofollow">http:/<wbr />/<wbr />example.org</a> test', +			'non-ascii http://ñew.org/ test' => +				'non-ascii <a href="http://ñew.org/" rel="nofollow">http:/<wbr />/<wbr />ñew.org/<wbr /></a> test', + +			// section 2.1 +			'percent encoded http://example.org/a%20b test' => +				'percent encoded <a href="http://example.org/a%20b" rel="nofollow">http:/<wbr />/<wbr />example.org/<wbr />a%20b</a> test', +			// section 2.2: skipping single quote and parenthese +			'reserved characters http://example.org/:/?#[]@!$&*+,;= test' => +				'reserved characters <a href="http://example.org/:/?#[]@!$&*+,;=" rel="nofollow">http:/<wbr />/<wbr />example.org/<wbr />:/<wbr />?#[]@!$&*+,;=</a> test', +			// section 2.3 +			'unreserved characters http://example.org/a1-._~ test' => +				'unreserved characters <a href="http://example.org/a1-._~" rel="nofollow">http:/<wbr />/<wbr />example.org/<wbr />a1-._~</a> test', + +			'parameters http://example.org/?val[]=1&val[]=2 test' => +				'parameters <a href="http://example.org/?val[]=1&val[]=2" rel="nofollow">http:/<wbr />/<wbr />example.org/<wbr />?val[]=1&val[]=2</a> test', +			'port http://example.org:80/ test' => +				'port <a href="http://example.org:80/" rel="nofollow">http:/<wbr />/<wbr />example.org:80/<wbr /></a> test', + +			'parentheses (http://www.google.com) test' => +				'parentheses (<a href="http://www.google.com" rel="nofollow">http:/<wbr />/<wbr />www.google.com</a>) test', +			'comma http://elgg.org, test' => +				'comma <a href="http://elgg.org" rel="nofollow">http:/<wbr />/<wbr />elgg.org</a>, test', +			'period http://elgg.org. test' => +				'period <a href="http://elgg.org" rel="nofollow">http:/<wbr />/<wbr />elgg.org</a>. test', +			'exclamation http://elgg.org! test' => +				'exclamation <a href="http://elgg.org" rel="nofollow">http:/<wbr />/<wbr />elgg.org</a>! test', + +			'already anchor <a href="http://twitter.com/">twitter</a> test' => +				'already anchor <a href="http://twitter.com/">twitter</a> test', + +			'ssl https://example.org/ test' => +				'ssl <a href="https://example.org/" rel="nofollow">https:/<wbr />/<wbr />example.org/<wbr /></a> test', +			'ftp ftp://example.org/ test' => +				'ftp <a href="ftp://example.org/" rel="nofollow">ftp:/<wbr />/<wbr />example.org/<wbr /></a> test', + +			'web archive anchor <a href="http://web.archive.org/web/20000229040250/http://www.google.com/">google</a>' => +				'web archive anchor <a href="http://web.archive.org/web/20000229040250/http://www.google.com/">google</a>', + +			'single quotes already anchor <a href=\'http://www.yahoo.com\'>yahoo</a>' => +				'single quotes already anchor <a href=\'http://www.yahoo.com\'>yahoo</a>', + +			'unquoted already anchor <a href=http://www.yahoo.com>yahoo</a>' => +				'unquoted already anchor <a href=http://www.yahoo.com>yahoo</a>', + +			'parens in uri http://thedailywtf.com/Articles/A-(Long-Overdue)-BuildMaster-Introduction.aspx' => +				'parens in uri <a href="http://thedailywtf.com/Articles/A-(Long-Overdue)-BuildMaster-Introduction.aspx" rel="nofollow">http:/<wbr />/<wbr />thedailywtf.com/<wbr />Articles/<wbr />A-(Long-Overdue)-BuildMaster-Introduction.aspx</a>' +		); +		foreach ($cases as $input => $output) { +			$this->assertEqual($output, parse_urls($input)); +		} +	} +	 +	/** +	 * Ensure additional select columns do not end up in entity attributes. +	 * +	 * https://github.com/Elgg/Elgg/issues/5538 +	 */ +	public function test_extra_columns_dont_appear_in_attributes() { +		global $ENTITY_CACHE; + +		// may not have groups in DB - let's create one +		$group = new ElggGroup(); +		$group->name = 'test_group'; +		$group->access_id = ACCESS_PUBLIC; +		$this->assertTrue($group->save() !== false); +		 +		// entity cache interferes with our test +		$ENTITY_CACHE = array(); +		 +		foreach (array('site', 'user', 'group', 'object') as $type) { +			$entities = elgg_get_entities(array( +				'type' => $type, +				'selects' => array('1 as _nonexistent_test_column'), +				'limit' => 1, +			)); +			if (!$this->assertTrue($entities, "Query for '$type' did not return an entity.")) { +				continue; +			} +			$entity = $entities[0]; +			$this->assertNull($entity->_nonexistent_test_column, "Additional select columns are leaking to attributes for '$type'"); +		} +		 +		$group->delete(); +	} + +	/** +	 * Ensure that ElggBatch doesn't go into infinite loop when disabling annotations recursively when show hidden is enabled. +	 * +	 * https://github.com/Elgg/Elgg/issues/5952 +	 */ +	public function test_disabling_annotations_infinite_loop() { + +		//let's have some entity +		$group = new ElggGroup(); +		$group->name = 'test_group'; +		$group->access_id = ACCESS_PUBLIC; +		$this->assertTrue($group->save() !== false); + +		$total = 51; +		//add some annotations +		for ($cnt = 0; $cnt < $total; $cnt++) { +			$group->annotate('test_annotation', 'value_' . $total); +		} + +		//disable them +		$show_hidden = access_get_show_hidden_status(); +		access_show_hidden_entities(true); +		$options = array( +			'guid' => $group->guid, +			'limit' => $total, //using strict limit to avoid real infinite loop and just see ElggBatch limiting on it before finishing the work +		); +		elgg_disable_annotations($options); +		access_show_hidden_entities($show_hidden); + +		//confirm all being disabled +		$annotations = $group->getAnnotations(array( +			'limit' => $total, +		)); +		foreach ($annotations as $annotation) { +			$this->assertTrue($annotation->enabled == 'no'); +		} + +		//delete group and annotations +		$group->delete(); +	} + +	public function test_ElggXMLElement_does_not_load_external_entities() { +		$elLast = libxml_disable_entity_loader(false); + +		// build payload that should trigger loading of external entity +		$payload = file_get_contents(dirname(dirname(__FILE__)) . '/test_files/xxe/request.xml'); +		$path = realpath(dirname(dirname(__FILE__)) . '/test_files/xxe/external_entity.txt'); +		$path = str_replace('\\', '/', $path); +		if ($path[0] != '/') { +			$path = '/' . $path; +		} +		$path = 'file://' . $path; +		$payload = sprintf($payload, $path); + +		// make sure we can actually this in this environment +		$element = new SimpleXMLElement($payload); +		$can_load_entity = preg_match('/secret/', (string)$element->methodName); + +		$this->skipUnless($can_load_entity, "XXE vulnerability cannot be tested on this system"); + +		if ($can_load_entity) { +			$el = new ElggXMLElement($payload); +			$chidren = $el->getChildren(); +			$content = $chidren[0]->getContent(); +			$this->assertNoPattern('/secret/', $content); +		} + +		libxml_disable_entity_loader($elLast); +	}  } diff --git a/engine/tests/test_files/plugin_18/manifest.xml b/engine/tests/test_files/plugin_18/manifest.xml index 5d788616a..c8b407511 100644 --- a/engine/tests/test_files/plugin_18/manifest.xml +++ b/engine/tests/test_files/plugin_18/manifest.xml @@ -7,7 +7,7 @@  	<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> +	<bugtracker>https://github.com/elgg/elgg/issues</bugtracker>  	<donations>http://elgg.org/supporter.php</donations>  	<copyright>(C) Elgg Foundation 2011</copyright>  	<license>GNU General Public License version 2</license> diff --git a/engine/tests/test_files/xxe/external_entity.txt b/engine/tests/test_files/xxe/external_entity.txt new file mode 100644 index 000000000..536aca34d --- /dev/null +++ b/engine/tests/test_files/xxe/external_entity.txt @@ -0,0 +1 @@ +secret
\ No newline at end of file diff --git a/engine/tests/test_files/xxe/request.xml b/engine/tests/test_files/xxe/request.xml new file mode 100644 index 000000000..4390f9db2 --- /dev/null +++ b/engine/tests/test_files/xxe/request.xml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<!DOCTYPE foo [ +<!ELEMENT methodName ANY > +<!ENTITY xxe SYSTEM "%s" > +]> +<methodCall> +    <methodName>test&xxe;test</methodName> +</methodCall>  | 
