diff options
Diffstat (limited to 'engine/classes')
| -rw-r--r-- | engine/classes/ElggAttributeLoader.php | 49 | ||||
| -rw-r--r-- | engine/classes/ElggAutoP.php | 14 | ||||
| -rw-r--r-- | engine/classes/ElggBatch.php | 68 | ||||
| -rw-r--r-- | engine/classes/ElggCrypto.php | 208 | ||||
| -rw-r--r-- | engine/classes/ElggDiskFilestore.php | 20 | ||||
| -rw-r--r-- | engine/classes/ElggEntity.php | 28 | ||||
| -rw-r--r-- | engine/classes/ElggFile.php | 9 | ||||
| -rw-r--r-- | engine/classes/ElggGroup.php | 9 | ||||
| -rw-r--r-- | engine/classes/ElggLRUCache.php | 181 | ||||
| -rw-r--r-- | engine/classes/ElggMenuBuilder.php | 8 | ||||
| -rw-r--r-- | engine/classes/ElggObject.php | 10 | ||||
| -rw-r--r-- | engine/classes/ElggPlugin.php | 25 | ||||
| -rw-r--r-- | engine/classes/ElggPluginPackage.php | 1 | ||||
| -rw-r--r-- | engine/classes/ElggSite.php | 2 | ||||
| -rw-r--r-- | engine/classes/ElggStaticVariableCache.php | 6 | ||||
| -rw-r--r-- | engine/classes/ElggTranslit.php | 11 | ||||
| -rw-r--r-- | engine/classes/ElggUser.php | 11 | ||||
| -rw-r--r-- | engine/classes/ElggWidget.php | 7 | ||||
| -rw-r--r-- | engine/classes/ElggXMLElement.php | 8 |
19 files changed, 599 insertions, 76 deletions
diff --git a/engine/classes/ElggAttributeLoader.php b/engine/classes/ElggAttributeLoader.php index d1e15008e..ffc80b02d 100644 --- a/engine/classes/ElggAttributeLoader.php +++ b/engine/classes/ElggAttributeLoader.php @@ -4,7 +4,7 @@ * Loads ElggEntity attributes from DB or validates those passed in via constructor * * @access private - * + * * @package Elgg.Core * @subpackage DataModel */ @@ -24,7 +24,7 @@ class ElggAttributeLoader { 'time_created', 'time_updated', 'last_action', - 'enabled' + 'enabled', ); /** @@ -69,7 +69,7 @@ class ElggAttributeLoader { /** * 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 @@ -94,7 +94,7 @@ class ElggAttributeLoader { /** * Get primary attributes missing that are missing - * + * * @param stdClass $row Database row * @return array */ @@ -104,7 +104,7 @@ class ElggAttributeLoader { /** * Get secondary attributes that are missing - * + * * @param stdClass $row Database row * @return array */ @@ -114,7 +114,7 @@ class ElggAttributeLoader { /** * Check that the type is correct - * + * * @param stdClass $row Database row * @return void * @throws InvalidClassException @@ -200,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; } @@ -209,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 71536c433..05842d1b2 100644 --- a/engine/classes/ElggAutoP.php +++ b/engine/classes/ElggAutoP.php @@ -110,12 +110,19 @@ class ElggAutoP { // 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]'); @@ -135,9 +142,16 @@ 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); diff --git a/engine/classes/ElggBatch.php b/engine/classes/ElggBatch.php index 5d59425d0..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; 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/ElggDiskFilestore.php b/engine/classes/ElggDiskFilestore.php index ded653436..6e2354012 100644 --- a/engine/classes/ElggDiskFilestore.php +++ b/engine/classes/ElggDiskFilestore.php @@ -194,7 +194,9 @@ 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 * @@ -213,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; } /** @@ -221,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()); @@ -235,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)); } @@ -248,7 +258,7 @@ 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; } @@ -335,7 +345,7 @@ class ElggDiskFilestore extends ElggFilestore { 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 diff --git a/engine/classes/ElggEntity.php b/engine/classes/ElggEntity.php index 5a63c7b15..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) @@ -352,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); @@ -379,7 +379,7 @@ abstract class ElggEntity extends ElggData implements // 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(); @@ -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; diff --git a/engine/classes/ElggFile.php b/engine/classes/ElggFile.php index 3e9c24c17..23080834b 100644 --- a/engine/classes/ElggFile.php +++ b/engine/classes/ElggFile.php @@ -275,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; } /** diff --git a/engine/classes/ElggGroup.php b/engine/classes/ElggGroup.php index 7ab0bfa48..7e69b7a84 100644 --- a/engine/classes/ElggGroup.php +++ b/engine/classes/ElggGroup.php @@ -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/ElggMenuBuilder.php b/engine/classes/ElggMenuBuilder.php index 198018f3c..b463143d8 100644 --- a/engine/classes/ElggMenuBuilder.php +++ b/engine/classes/ElggMenuBuilder.php @@ -128,8 +128,10 @@ class ElggMenuBuilder { $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]); } } @@ -163,7 +165,7 @@ class ElggMenuBuilder { // scan looking for a selected item foreach ($this->menu as $menu_item) { if ($menu_item->getHref()) { - if (elgg_http_url_is_identical(full_url(), $menu_item->getHref())) { + if (elgg_http_url_is_identical(current_page_url(), $menu_item->getHref())) { $menu_item->setSelected(true); return $menu_item; } diff --git a/engine/classes/ElggObject.php b/engine/classes/ElggObject.php index 3cb76ffaf..aeaa3ba5c 100644 --- a/engine/classes/ElggObject.php +++ b/engine/classes/ElggObject.php @@ -107,7 +107,7 @@ class ElggObject extends ElggEntity { $this->attributes = $attrs; $this->attributes['tables_loaded'] = 2; - cache_entity($this); + _elgg_cache_entity($this); return true; } @@ -126,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 c1c46f272..545b9a53c 100644 --- a/engine/classes/ElggPlugin.php +++ b/engine/classes/ElggPlugin.php @@ -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 */ diff --git a/engine/classes/ElggPluginPackage.php b/engine/classes/ElggPluginPackage.php index 209242288..37eb4bf4d 100644 --- a/engine/classes/ElggPluginPackage.php +++ b/engine/classes/ElggPluginPackage.php @@ -294,6 +294,7 @@ class ElggPluginPackage { return true; } + $this->errorMsg = elgg_echo('unknown_error'); return false; } diff --git a/engine/classes/ElggSite.php b/engine/classes/ElggSite.php index deba5087e..dd996fe98 100644 --- a/engine/classes/ElggSite.php +++ b/engine/classes/ElggSite.php @@ -124,7 +124,7 @@ class ElggSite extends ElggEntity { $this->attributes = $attrs; $this->attributes['tables_loaded'] = 2; - cache_entity($this); + _elgg_cache_entity($this); return true; } 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 601965c11..b4bf87797 100644 --- a/engine/classes/ElggTranslit.php +++ b/engine/classes/ElggTranslit.php @@ -49,10 +49,15 @@ 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 diff --git a/engine/classes/ElggUser.php b/engine/classes/ElggUser.php index b80065b27..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; } @@ -112,7 +115,7 @@ class ElggUser extends ElggEntity $this->attributes = $attrs; $this->attributes['tables_loaded'] = 2; - cache_entity($this); + _elgg_cache_entity($this); return true; } @@ -129,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/ElggWidget.php b/engine/classes/ElggWidget.php index c123e5032..66191bf47 100644 --- a/engine/classes/ElggWidget.php +++ b/engine/classes/ElggWidget.php @@ -146,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 6f2633e25..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); } } @@ -123,5 +128,4 @@ class ElggXMLElement { } return false; } - -}
\ No newline at end of file +} |
