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