diff options
Diffstat (limited to 'engine/lib')
28 files changed, 959 insertions, 510 deletions
diff --git a/engine/lib/access.php b/engine/lib/access.php index e8b3b0d52..f7d3bf7ea 100644 --- a/engine/lib/access.php +++ b/engine/lib/access.php @@ -12,6 +12,26 @@ */ /** + * Return an ElggCache static variable cache for the access caches + * + * @staticvar ElggStaticVariableCache $access_cache + * @return \ElggStaticVariableCache + * @access private + */ +function _elgg_get_access_cache() { + /** + * A default filestore cache using the dataroot. + */ + static $access_cache; + + if (!$access_cache) { + $access_cache = new ElggStaticVariableCache('access'); + } + + return $access_cache; +} + +/** * Return a string of access_ids for $user_id appropriate for inserting into an SQL IN clause. * * @uses get_access_array @@ -29,10 +49,10 @@ */ function get_access_list($user_id = 0, $site_id = 0, $flush = false) { global $CONFIG, $init_finished; - static $access_list; - - if (!isset($access_list)) { - $access_list = array(); + $cache = _elgg_get_access_cache(); + + if ($flush) { + $cache->clear(); } if ($user_id == 0) { @@ -45,20 +65,20 @@ function get_access_list($user_id = 0, $site_id = 0, $flush = false) { $user_id = (int) $user_id; $site_id = (int) $site_id; - if (isset($access_list[$user_id]) && $flush == false) { - return $access_list[$user_id]; - } + $hash = $user_id . $site_id . 'get_access_list'; - $access = "(" . implode(",", get_access_array($user_id, $site_id, $flush)) . ")"; + if ($cache[$hash]) { + return $cache[$hash]; + } + + $access_array = get_access_array($user_id, $site_id, $flush); + $access = "(" . implode(",", $access_array) . ")"; - // only cache if done with init and access is enabled (unless admin user) - // session is loaded before init is finished, so don't need to check for user session - if ($init_finished && (elgg_is_admin_logged_in() || !elgg_get_ignore_access())) { - $access_list[$user_id] = $access; - return $access_list[$user_id]; - } else { - return $access; + if ($init_finished) { + $cache[$hash] = $access; } + + return $access; } /** @@ -86,12 +106,10 @@ function get_access_list($user_id = 0, $site_id = 0, $flush = false) { function get_access_array($user_id = 0, $site_id = 0, $flush = false) { global $CONFIG, $init_finished; - // @todo everything from the db is cached. - // this cache might be redundant. But db cache is flushed on every db write. - static $access_array; + $cache = _elgg_get_access_cache(); - if (!isset($access_array)) { - $access_array = array(); + if ($flush) { + $cache->clear(); } if ($user_id == 0) { @@ -105,35 +123,41 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) { $user_id = (int) $user_id; $site_id = (int) $site_id; - if (empty($access_array[$user_id]) || $flush == true) { - $tmp_access_array = array(ACCESS_PUBLIC); + $hash = $user_id . $site_id . 'get_access_array'; + + if ($cache[$hash]) { + $access_array = $cache[$hash]; + } else { + $access_array = array(ACCESS_PUBLIC); // The following can only return sensible data if the user is logged in. if (elgg_is_logged_in()) { - $tmp_access_array[] = ACCESS_LOGGED_IN; + $access_array[] = ACCESS_LOGGED_IN; // Get ACL memberships $query = "SELECT am.access_collection_id" . " FROM {$CONFIG->dbprefix}access_collection_membership am" . " LEFT JOIN {$CONFIG->dbprefix}access_collections ag ON ag.id = am.access_collection_id" - . " WHERE am.user_guid = {$user_id} AND (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; + . " WHERE am.user_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)"; - if ($collections = get_data($query)) { + $collections = get_data($query); + if ($collections) { foreach ($collections as $collection) { if (!empty($collection->access_collection_id)) { - $tmp_access_array[] = (int)$collection->access_collection_id; + $access_array[] = (int)$collection->access_collection_id; } } } // Get ACLs owned. $query = "SELECT ag.id FROM {$CONFIG->dbprefix}access_collections ag "; - $query .= "WHERE ag.owner_guid = {$user_id} AND (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; + $query .= "WHERE ag.owner_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)"; - if ($collections = get_data($query)) { + $collections = get_data($query); + if ($collections) { foreach ($collections as $collection) { if (!empty($collection->id)) { - $tmp_access_array[] = (int)$collection->id; + $access_array[] = (int)$collection->id; } } } @@ -141,21 +165,21 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) { $ignore_access = elgg_check_access_overrides($user_id); if ($ignore_access == true) { - $tmp_access_array[] = ACCESS_PRIVATE; + $access_array[] = ACCESS_PRIVATE; } + } - // only cache if done with init and access is enabled (unless admin user) - // session is loaded before init is finished, so don't need to check for user session - if ($init_finished && (elgg_is_admin_logged_in() || !elgg_get_ignore_access())) { - $access_array[$user_id] = $tmp_access_array; - } + if ($init_finished) { + $cache[$hash] = $access_array; } - } else { - $tmp_access_array = $access_array[$user_id]; } - $options = array('user_id' => $user_id, 'site_id' => $site_id); - return elgg_trigger_plugin_hook('access:collections:read', 'user', $options, $tmp_access_array); + $options = array( + 'user_id' => $user_id, + 'site_id' => $site_id + ); + + return elgg_trigger_plugin_hook('access:collections:read', 'user', $options, $access_array); } /** @@ -401,9 +425,12 @@ function has_access_to_entity($entity, $user = null) { * @link http://docs.elgg.org/Access */ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) { - global $CONFIG; - //@todo this is probably not needed since caching happens at the DB level. - static $access_array; + global $CONFIG, $init_finished; + $cache = _elgg_get_access_cache(); + + if ($flush) { + $cache->clear(); + } if ($user_id == 0) { $user_id = elgg_get_logged_in_user_guid(); @@ -416,37 +443,41 @@ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) { $user_id = (int) $user_id; $site_id = (int) $site_id; - if (empty($access_array[$user_id]) || $flush == true) { - $query = "SELECT ag.* FROM {$CONFIG->dbprefix}access_collections ag "; - $query .= " WHERE (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; - $query .= " AND (ag.owner_guid = {$user_id})"; - // ACCESS_PRIVATE through ACCESS_PUBLIC take 0 through 2 - // @todo this AND clause is unnecessary because of id starts at 3 for table - $query .= " AND ag.id >= 3"; + $hash = $user_id . $site_id . 'get_write_access_array'; - $tmp_access_array = array( + if ($cache[$hash]) { + $access_array = $cache[$hash]; + } else { + // @todo is there such a thing as public write access? + $access_array = array( ACCESS_PRIVATE => elgg_echo("PRIVATE"), ACCESS_FRIENDS => elgg_echo("access:friends:label"), ACCESS_LOGGED_IN => elgg_echo("LOGGED_IN"), ACCESS_PUBLIC => elgg_echo("PUBLIC") ); + + $query = "SELECT ag.* FROM {$CONFIG->dbprefix}access_collections ag "; + $query .= " WHERE (ag.site_guid = $site_id OR ag.site_guid = 0)"; + $query .= " AND (ag.owner_guid = $user_id)"; + $collections = get_data($query); if ($collections) { foreach ($collections as $collection) { - $tmp_access_array[$collection->id] = $collection->name; + $access_array[$collection->id] = $collection->name; } } - $access_array[$user_id] = $tmp_access_array; - } else { - $tmp_access_array = $access_array[$user_id]; + if ($init_finished) { + $cache[$hash] = $access_array; + } } - $options = array('user_id' => $user_id, 'site_id' => $site_id); - $tmp_access_array = elgg_trigger_plugin_hook('access:collections:write', 'user', - $options, $tmp_access_array); - - return $tmp_access_array; + $options = array( + 'user_id' => $user_id, + 'site_id' => $site_id + ); + return elgg_trigger_plugin_hook('access:collections:write', 'user', + $options, $access_array); } /** @@ -476,7 +507,7 @@ function can_edit_access_collection($collection_id, $user_guid = null) { return false; } - $write_access = get_write_access_array($user->getGUID(), null, true); + $write_access = get_write_access_array($user->getGUID(), 0, true); // don't ignore access when checking users. if ($user_guid) { @@ -560,8 +591,6 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { * @see remove_user_from_access_collection() */ function update_access_collection($collection_id, $members) { - global $CONFIG; - $acl = get_access_collection($collection_id); if (!$acl) { @@ -877,6 +906,8 @@ function get_readable_access_level($entity_access_id) { * @tip Use this to access entities in automated scripts * when no user is logged in. * + * @note This clears the access cache. + * * @warning This will not show disabled entities. * Use {@link access_show_hidden_entities()} to access disabled entities. * @@ -888,6 +919,8 @@ function get_readable_access_level($entity_access_id) { * @see elgg_get_ignore_access() */ function elgg_set_ignore_access($ignore = true) { + $cache = _elgg_get_access_cache(); + $cache->clear(); $elgg_access = elgg_get_access_object(); return $elgg_access->setIgnoreAccess($ignore); } @@ -1018,6 +1051,7 @@ function elgg_override_permissions($hook, $type, $value, $params) { */ function access_test($hook, $type, $value, $params) { global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/access_collections.php'; return $value; } diff --git a/engine/lib/admin.php b/engine/lib/admin.php index b65d98c95..35ab5599d 100644 --- a/engine/lib/admin.php +++ b/engine/lib/admin.php @@ -233,6 +233,7 @@ function admin_init() { elgg_register_action('admin/site/update_basic', '', 'admin'); elgg_register_action('admin/site/update_advanced', '', 'admin'); elgg_register_action('admin/site/flush_cache', '', 'admin'); + elgg_register_action('admin/site/unlock_upgrade', '', 'admin'); elgg_register_action('admin/menu/save', '', 'admin'); @@ -268,8 +269,9 @@ function admin_init() { // users elgg_register_admin_menu_item('administer', 'users', null, 20); elgg_register_admin_menu_item('administer', 'online', 'users', 10); - elgg_register_admin_menu_item('administer', 'newest', 'users', 20); - elgg_register_admin_menu_item('administer', 'add', 'users', 30); + elgg_register_admin_menu_item('administer', 'admins', 'users', 20); + elgg_register_admin_menu_item('administer', 'newest', 'users', 30); + elgg_register_admin_menu_item('administer', 'add', 'users', 40); // configure // plugins @@ -421,7 +423,7 @@ function admin_pagesetup() { elgg_register_menu_item('admin_footer', array( 'name' => 'community_forums', 'text' => elgg_echo('admin:footer:community_forums'), - 'href' => 'http://community.elgg.org/pg/groups/world/', + 'href' => 'http://community.elgg.org/groups/all/', )); elgg_register_menu_item('admin_footer', array( diff --git a/engine/lib/annotations.php b/engine/lib/annotations.php index 2036ccd61..3b9f84703 100644 --- a/engine/lib/annotations.php +++ b/engine/lib/annotations.php @@ -316,8 +316,6 @@ function elgg_list_annotations($options) { * * annotation_owner_guids => NULL|ARR guids for annotaiton owners * - * annotation_ids => NULL|ARR Annotation IDs - * * @return mixed If count, int. If not count, array. false on errors. * @since 1.7.0 */ @@ -336,8 +334,6 @@ function elgg_get_entities_from_annotations(array $options = array()) { 'annotation_owner_guids' => ELGG_ENTITIES_ANY_VALUE, - 'annotation_ids' => ELGG_ENTITIES_ANY_VALUE, - 'order_by' => 'maxtime desc', 'group_by' => 'a.entity_guid' ); @@ -345,12 +341,13 @@ function elgg_get_entities_from_annotations(array $options = array()) { $options = array_merge($defaults, $options); $singulars = array('annotation_name', 'annotation_value', - 'annotation_name_value_pair', 'annotation_owner_guid', 'annotation_id'); + 'annotation_name_value_pair', 'annotation_owner_guid'); $options = elgg_normalise_plural_options_array($options, $singulars); + $options = elgg_entities_get_metastrings_options('annotation', $options); - if (!$options = elgg_entities_get_metastrings_options('annotation', $options)) { - return FALSE; + if (!$options) { + return false; } // special sorting for annotations diff --git a/engine/lib/configuration.php b/engine/lib/configuration.php index 305aa00b6..385021581 100644 --- a/engine/lib/configuration.php +++ b/engine/lib/configuration.php @@ -91,23 +91,29 @@ function elgg_get_config($name, $site_guid = 0) { return $CONFIG->$name; } - if ($site_guid === NULL) { + if ($site_guid === null) { // installation wide setting $value = datalist_get($name); } else { - // site specific setting - if ($site_guid == 0) { - $site_guid = (int) $CONFIG->site_id; + // hit DB only if we're not sure if value exists or not + if (!isset($CONFIG->site_config_loaded)) { + // site specific setting + if ($site_guid == 0) { + $site_guid = (int) $CONFIG->site_id; + } + $value = get_config($name, $site_guid); + } else { + $value = null; } - $value = get_config($name, $site_guid); } - if ($value !== false) { - $CONFIG->$name = $value; - return $value; + // @todo document why we don't cache false + if ($value === false) { + return null; } - return null; + $CONFIG->$name = $value; + return $value; } /** @@ -132,7 +138,7 @@ function elgg_set_config($name, $value) { /** * Save a configuration setting * - * @param string $name Configuration name (cannot be greater than 32 characters) + * @param string $name Configuration name (cannot be greater than 255 characters) * @param mixed $value Configuration value. Should be string for installation setting * @param int $site_guid NULL for installation setting, 0 for default site * @@ -221,9 +227,9 @@ function datalist_get($name) { $name = trim($name); - // cannot store anything longer than 32 characters in db, so catch here - if (elgg_strlen($name) > 32) { - elgg_log("The name length for configuration variables cannot be greater than 32", "ERROR"); + // cannot store anything longer than 255 characters in db, so catch here + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); return false; } @@ -280,7 +286,7 @@ function datalist_get($name) { function datalist_set($name, $value) { global $CONFIG, $DATALIST_CACHE; - // cannot store anything longer than 32 characters in db, so catch before we set + // cannot store anything longer than 255 characters in db, so catch before we set if (elgg_strlen($name) > 255) { elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); return false; @@ -326,7 +332,7 @@ function datalist_set($name, $value) { * This will cause the run once function to be run on all installations. To perform * additional upgrades, create new functions for each release. * - * @warning The function name cannot be longer than 32 characters long due to + * @warning The function name cannot be longer than 255 characters long due to * the current schema for the datalist table. * * @internal A datalist entry $functioname is created with the value of time(). @@ -413,9 +419,9 @@ function set_config($name, $value, $site_guid = 0) { $name = trim($name); - // cannot store anything longer than 32 characters in db, so catch before we set - if (elgg_strlen($name) > 32) { - elgg_log("The name length for configuration variables cannot be greater than 32", "ERROR"); + // cannot store anything longer than 255 characters in db, so catch before we set + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); return false; } @@ -558,6 +564,8 @@ function _elgg_load_site_config() { $CONFIG->url = $CONFIG->wwwroot; get_all_config(); + // gives hint to elgg_get_config function how to approach missing values + $CONFIG->site_config_loaded = true; } /** diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index 554b0561f..540605876 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -671,7 +671,7 @@ function elgg_register_event_handler($event, $object_type, $callback, $priority global $CONFIG; if (empty($event) || empty($object_type)) { - return FALSE; + return false; } if (!isset($CONFIG->events)) { @@ -684,8 +684,8 @@ function elgg_register_event_handler($event, $object_type, $callback, $priority $CONFIG->events[$event][$object_type] = array(); } - if (!is_callable($callback)) { - return FALSE; + if (!is_callable($callback, true)) { + return false; } $priority = max((int) $priority, 0); @@ -695,7 +695,7 @@ function elgg_register_event_handler($event, $object_type, $callback, $priority } $CONFIG->events[$event][$object_type][$priority] = $callback; ksort($CONFIG->events[$event][$object_type]); - return TRUE; + return true; } /** @@ -710,9 +710,12 @@ function elgg_register_event_handler($event, $object_type, $callback, $priority */ function elgg_unregister_event_handler($event, $object_type, $callback) { global $CONFIG; - foreach ($CONFIG->events[$event][$object_type] as $key => $event_callback) { - if ($event_callback == $callback) { - unset($CONFIG->events[$event][$object_type][$key]); + + if (isset($CONFIG->events[$event]) && isset($CONFIG->events[$event][$object_type])) { + foreach ($CONFIG->events[$event][$object_type] as $key => $event_callback) { + if ($event_callback == $callback) { + unset($CONFIG->events[$event][$object_type][$key]); + } } } } @@ -770,14 +773,14 @@ function elgg_trigger_event($event, $object_type, $object = null) { foreach ($events as $callback_list) { if (is_array($callback_list)) { foreach ($callback_list as $callback) { - if (call_user_func_array($callback, $args) === FALSE) { - return FALSE; + if (is_callable($callback) && (call_user_func_array($callback, $args) === false)) { + return false; } } } } - return TRUE; + return true; } /** @@ -850,7 +853,7 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority = global $CONFIG; if (empty($hook) || empty($type)) { - return FALSE; + return false; } if (!isset($CONFIG->hooks)) { @@ -863,8 +866,8 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority = $CONFIG->hooks[$hook][$type] = array(); } - if (!is_callable($callback)) { - return FALSE; + if (!is_callable($callback, true)) { + return false; } $priority = max((int) $priority, 0); @@ -874,7 +877,7 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority = } $CONFIG->hooks[$hook][$type][$priority] = $callback; ksort($CONFIG->hooks[$hook][$type]); - return TRUE; + return true; } /** @@ -889,9 +892,12 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority = */ function elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback) { global $CONFIG; - foreach ($CONFIG->hooks[$hook][$entity_type] as $key => $hook_callback) { - if ($hook_callback == $callback) { - unset($CONFIG->hooks[$hook][$entity_type][$key]); + + if (isset($CONFIG->hooks[$hook]) && isset($CONFIG->hooks[$hook][$entity_type])) { + foreach ($CONFIG->hooks[$hook][$entity_type] as $key => $hook_callback) { + if ($hook_callback == $callback) { + unset($CONFIG->hooks[$hook][$entity_type][$key]); + } } } } @@ -970,10 +976,12 @@ function elgg_trigger_plugin_hook($hook, $type, $params = null, $returnvalue = n foreach ($hooks as $callback_list) { if (is_array($callback_list)) { foreach ($callback_list as $hookcallback) { - $args = array($hook, $type, $returnvalue, $params); - $temp_return_value = call_user_func_array($hookcallback, $args); - if (!is_null($temp_return_value)) { - $returnvalue = $temp_return_value; + if (is_callable($hookcallback)) { + $args = array($hook, $type, $returnvalue, $params); + $temp_return_value = call_user_func_array($hookcallback, $args); + if (!is_null($temp_return_value)) { + $returnvalue = $temp_return_value; + } } } } @@ -1070,7 +1078,11 @@ function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { case E_WARNING : case E_USER_WARNING : case E_RECOVERABLE_ERROR: // (e.g. type hint violation) - error_log("PHP WARNING: $error"); + + // check if the error wasn't suppressed by the error control operator (@) + if (error_reporting()) { + error_log("PHP WARNING: $error"); + } break; default: @@ -1879,7 +1891,7 @@ function elgg_cacheable_view_page_handler($page, $type) { header("Content-type: $content_type"); // @todo should js be cached when simple cache turned off - //header('Expires: ' . date('r', time() + 864000)); + //header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', strtotime("+10 days")), true); //header("Pragma: public"); //header("Cache-Control: public"); //header("Content-Length: " . strlen($return)); diff --git a/engine/lib/entities.php b/engine/lib/entities.php index 3896cd58f..ce736ce05 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -17,13 +17,13 @@ global $ENTITY_CACHE; $ENTITY_CACHE = array(); /** - * Cache subtypes and related class names once loaded. + * Cache subtypes and related class names. * - * @global array $SUBTYPE_CACHE + * @global array|null $SUBTYPE_CACHE array once populated from DB, initially null * @access private */ global $SUBTYPE_CACHE; -$SUBTYPE_CACHE = NULL; +$SUBTYPE_CACHE = null; /** * Invalidate this class's entry in the cache. @@ -39,6 +39,8 @@ function invalidate_cache_for_entity($guid) { $guid = (int)$guid; unset($ENTITY_CACHE[$guid]); + + elgg_get_metadata_cache()->clear($guid); } /** @@ -57,16 +59,24 @@ function invalidate_cache_for_entity($guid) { function cache_entity(ElggEntity $entity) { global $ENTITY_CACHE; - // Don't cache entities while access control is off, otherwise they could be + // Don't cache non-plugin entities while access control is off, otherwise they could be // exposed to users who shouldn't see them when control is re-enabled. - if (elgg_get_ignore_access()) { + if (!($entity instanceof ElggPlugin) && elgg_get_ignore_access()) { return; } // Don't store too many or we'll have memory problems // TODO(evan): Pick a less arbitrary limit if (count($ENTITY_CACHE) > 256) { - unset($ENTITY_CACHE[array_rand($ENTITY_CACHE)]); + $random_guid = array_rand($ENTITY_CACHE); + + unset($ENTITY_CACHE[$random_guid]); + + // Purge separate metadata cache. Original idea was to do in entity destructor, but that would + // have caused a bunch of unnecessary purges at every shutdown. Doing it this way we have no way + // to know that the expunged entity will be GCed (might be another reference living), but that's + // OK; the metadata will reload if necessary. + elgg_get_metadata_cache()->clear($random_guid); } $ENTITY_CACHE[$entity->guid] = $entity; @@ -85,8 +95,6 @@ function cache_entity(ElggEntity $entity) { function retrieve_cached_entity($guid) { global $ENTITY_CACHE; - $guid = (int)$guid; - if (isset($ENTITY_CACHE[$guid])) { if ($ENTITY_CACHE[$guid]->isFullyLoaded()) { return $ENTITY_CACHE[$guid]; @@ -146,29 +154,23 @@ function retrieve_cached_entity_row($guid) { * @access private */ function get_subtype_id($type, $subtype) { - global $CONFIG, $SUBTYPE_CACHE; - - $type = sanitise_string($type); - $subtype = sanitise_string($subtype); + global $SUBTYPE_CACHE; - if ($subtype == "") { - return FALSE; + if (!$subtype) { + return false; } - // @todo use the cache before hitting database - $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes - where type='$type' and subtype='$subtype'"); - - if ($result) { - if (!$SUBTYPE_CACHE) { - $SUBTYPE_CACHE = array(); - } + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } - $SUBTYPE_CACHE[$result->id] = $result; + // use the cache before hitting database + $result = _elgg_retrieve_cached_subtype($type, $subtype); + if ($result !== null) { return $result->id; } - return FALSE; + return false; } /** @@ -176,35 +178,67 @@ function get_subtype_id($type, $subtype) { * * @param int $subtype_id Subtype ID * - * @return string Subtype name + * @return string|false Subtype name, false if subtype not found * @link http://docs.elgg.org/DataModel/Entities/Subtypes * @see get_subtype_from_id() * @access private */ function get_subtype_from_id($subtype_id) { - global $CONFIG, $SUBTYPE_CACHE; - - $subtype_id = (int)$subtype_id; + global $SUBTYPE_CACHE; if (!$subtype_id) { return false; } + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + if (isset($SUBTYPE_CACHE[$subtype_id])) { return $SUBTYPE_CACHE[$subtype_id]->subtype; } - $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where id=$subtype_id"); - if ($result) { - if (!$SUBTYPE_CACHE) { - $SUBTYPE_CACHE = array(); - } + return false; +} + +/** + * Retrieve subtype from the cache. + * + * @param string $type + * @param string $subtype + * @return stdClass|null + * + * @access private + */ +function _elgg_retrieve_cached_subtype($type, $subtype) { + global $SUBTYPE_CACHE; - $SUBTYPE_CACHE[$subtype_id] = $result; - return $result->subtype; + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); } - return false; + foreach ($SUBTYPE_CACHE as $obj) { + if ($obj->type === $type && $obj->subtype === $subtype) { + return $obj; + } + } + return null; +} + +/** + * Fetch all suptypes from DB to local cache. + * + * @access private + */ +function _elgg_populate_subtype_cache() { + global $CONFIG, $SUBTYPE_CACHE; + + $results = get_data("SELECT * FROM {$CONFIG->dbprefix}entity_subtypes"); + + $SUBTYPE_CACHE = array(); + foreach ($results as $row) { + $SUBTYPE_CACHE[$row->id] = $row; + } } /** @@ -223,25 +257,19 @@ function get_subtype_from_id($subtype_id) { * @access private */ function get_subtype_class($type, $subtype) { - global $CONFIG, $SUBTYPE_CACHE; - - $type = sanitise_string($type); - $subtype = sanitise_string($subtype); + global $SUBTYPE_CACHE; - // @todo use the cache before going to the database - $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes - where type='$type' and subtype='$subtype'"); - - if ($result) { - if (!$SUBTYPE_CACHE) { - $SUBTYPE_CACHE = array(); - } - - $SUBTYPE_CACHE[$result->id] = $result; - return $result->class; + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + + // use the cache before going to the database + $obj = _elgg_retrieve_cached_subtype($type, $subtype); + if ($obj) { + return $obj->class; } - return NULL; + return null; } /** @@ -255,29 +283,21 @@ function get_subtype_class($type, $subtype) { * @access private */ function get_subtype_class_from_id($subtype_id) { - global $CONFIG, $SUBTYPE_CACHE; - - $subtype_id = (int)$subtype_id; + global $SUBTYPE_CACHE; if (!$subtype_id) { - return false; + return null; } + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + if (isset($SUBTYPE_CACHE[$subtype_id])) { return $SUBTYPE_CACHE[$subtype_id]->class; } - $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where id=$subtype_id"); - - if ($result) { - if (!$SUBTYPE_CACHE) { - $SUBTYPE_CACHE = array(); - } - $SUBTYPE_CACHE[$subtype_id] = $result; - return $result->class; - } - - return NULL; + return null; } /** @@ -303,21 +323,32 @@ function get_subtype_class_from_id($subtype_id) { * @see get_entity() */ function add_subtype($type, $subtype, $class = "") { - global $CONFIG; - $type = sanitise_string($type); - $subtype = sanitise_string($subtype); - $class = sanitise_string($class); + global $CONFIG, $SUBTYPE_CACHE; - // Short circuit if no subtype is given - if ($subtype == "") { + if (!$subtype) { return 0; } $id = get_subtype_id($type, $subtype); - if ($id == 0) { - return insert_data("insert into {$CONFIG->dbprefix}entity_subtypes" - . " (type, subtype, class) values ('$type','$subtype','$class')"); + if (!$id) { + // In cache we store non-SQL-escaped strings because that's what's returned by query + $cache_obj = (object) array( + 'type' => $type, + 'subtype' => $subtype, + 'class' => $class, + ); + + $type = sanitise_string($type); + $subtype = sanitise_string($subtype); + $class = sanitise_string($class); + + $id = insert_data("INSERT INTO {$CONFIG->dbprefix}entity_subtypes" + . " (type, subtype, class) VALUES ('$type', '$subtype', '$class')"); + + // add entry to cache + $cache_obj->id = $id; + $SUBTYPE_CACHE[$id] = $cache_obj; } return $id; @@ -359,22 +390,31 @@ function remove_subtype($type, $subtype) { function update_subtype($type, $subtype, $class = '') { global $CONFIG, $SUBTYPE_CACHE; - if (!$id = get_subtype_id($type, $subtype)) { - return FALSE; + $id = get_subtype_id($type, $subtype); + if (!$id) { + return false; } + + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + + $unescaped_class = $class; + $type = sanitise_string($type); $subtype = sanitise_string($subtype); - - $result = update_data("UPDATE {$CONFIG->dbprefix}entity_subtypes + $class = sanitise_string($class); + + $success = update_data("UPDATE {$CONFIG->dbprefix}entity_subtypes SET type = '$type', subtype = '$subtype', class = '$class' WHERE id = $id "); - if ($result && isset($SUBTYPE_CACHE[$id])) { - $SUBTYPE_CACHE[$id]->class = $class; + if ($success && isset($SUBTYPE_CACHE[$id])) { + $SUBTYPE_CACHE[$id]->class = $unescaped_class; } - return $result; + return $success; } /** @@ -606,12 +646,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)) { @@ -698,7 +740,7 @@ function get_entity($guid) { // 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 @@ -715,14 +757,29 @@ function get_entity($guid) { $shared_cache = false; } } + + // until ACLs in memcache, DB query is required to determine access + $entity_row = get_entity_as_row($guid); + if (!$entity_row) { + return false; + } + if ($shared_cache) { - $new_entity = $shared_cache->load($guid); - if ($new_entity) { - return $new_entity; + $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; } } - $new_entity = entity_row_to_elggstar(get_entity_as_row($guid)); + // don't let incomplete entities cause fatal exceptions + try { + $new_entity = entity_row_to_elggstar($entity_row); + } catch (IncompleteEntityException $e) { + return false; + } + if ($new_entity) { cache_entity($new_entity); } @@ -967,19 +1024,32 @@ function elgg_get_entities(array $options = array()) { $query .= " LIMIT $offset, $limit"; } - $dt = get_data($query, $options['callback']); + if ($options['callback'] === 'entity_row_to_elggstar') { + $dt = _elgg_fetch_entities_from_sql($query); + } else { + $dt = get_data($query, $options['callback']); + } + if ($dt) { - foreach ($dt as $entity) { - // If a custom callback is provided, it could return something other than ElggEntity, - // so we have to do an explicit check here. - if ($entity instanceof ElggEntity) { - cache_entity($entity); + // 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); @@ -988,6 +1058,97 @@ function elgg_get_entities(array $options = array()) { } /** + * Return entities from an SQL query generated by elgg_get_entities. + * + * @param string $sql + * @return ElggEntity[] + * + * @access private + * @throws LogicException + */ +function _elgg_fetch_entities_from_sql($sql) { + static $plugin_subtype; + if (null === $plugin_subtype) { + $plugin_subtype = get_subtype_id('object', 'plugin'); + } + + // Keys are types, values are columns that, if present, suggest that the secondary + // table is already JOINed + $types_to_optimize = array( + 'object' => 'title', + 'user' => 'password', + 'group' => 'name', + ); + + $rows = get_data($sql); + + // guids to look up in each type + $lookup_types = array(); + // maps GUIDs to the $rows key + $guid_to_key = array(); + + if (isset($rows[0]->type, $rows[0]->subtype) + && $rows[0]->type === 'object' + && $rows[0]->subtype == $plugin_subtype) { + // Likely the entire resultset is plugins, which have already been optimized + // to JOIN the secondary table. In this case we allow retrieving from cache, + // but abandon the extra queries. + $types_to_optimize = array(); + } + + // First pass: use cache where possible, gather GUIDs that we're optimizing + foreach ($rows as $i => $row) { + if (empty($row->guid) || empty($row->type)) { + throw new LogicException('Entity row missing guid or type'); + } + if ($entity = retrieve_cached_entity($row->guid)) { + $rows[$i] = $entity; + continue; + } + if (isset($types_to_optimize[$row->type])) { + // check if row already looks JOINed. + if (isset($row->{$types_to_optimize[$row->type]})) { + // Row probably already contains JOINed secondary table. Don't make another query just + // to pull data that's already there + continue; + } + $lookup_types[$row->type][] = $row->guid; + $guid_to_key[$row->guid] = $i; + } + } + // Do secondary queries and merge rows + if ($lookup_types) { + $dbprefix = elgg_get_config('dbprefix'); + } + foreach ($lookup_types as $type => $guids) { + $set = "(" . implode(',', $guids) . ")"; + $sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set"; + $secondary_rows = get_data($sql); + if ($secondary_rows) { + foreach ($secondary_rows as $secondary_row) { + $key = $guid_to_key[$secondary_row->guid]; + // cast to arrays to merge then cast back + $rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row); + } + } + } + // Second pass to finish conversion + foreach ($rows as $i => $row) { + if ($row instanceof ElggEntity) { + continue; + } else { + try { + $rows[$i] = entity_row_to_elggstar($row); + } catch (IncompleteEntityException $e) { + // don't let incomplete entities throw fatal errors + unset($rows[$i]); + } + } + } + return $rows; +} + +/** * Returns SQL where clause for type and subtype on main entity table * * @param string $table Entity table prefix as defined in SELECT...FROM entities $table @@ -1153,7 +1314,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 */ @@ -1202,7 +1363,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 */ @@ -1304,7 +1465,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 */ @@ -1649,7 +1810,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 @@ -1694,6 +1855,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 @@ -1736,6 +1899,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'); @@ -1747,7 +1912,7 @@ function oddentity_to_elggentity(ODDEntity $element) { if (!$tmp) { // Construct new class with owner from session $classname = get_subtype_class($class, $subclass); - if ($classname != "") { + if ($classname) { if (class_exists($classname)) { $tmp = new $classname(); @@ -1807,11 +1972,13 @@ 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']; - $tmp = NULL; + $tmp = null; if ($element instanceof ODDEntity) { $tmp = oddentity_to_elggentity($element); @@ -1853,8 +2020,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) { @@ -1978,7 +2143,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 @@ -1986,7 +2151,7 @@ function get_entity_url($entity_guid) { function elgg_register_entity_url_handler($entity_type, $entity_subtype, $function_name) { global $CONFIG; - if (!is_callable($function_name)) { + if (!is_callable($function_name, true)) { return false; } @@ -2014,7 +2179,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 @@ -2051,7 +2216,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) { @@ -2118,7 +2283,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; @@ -2318,7 +2483,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 43421342c..538f601e1 100644 --- a/engine/lib/extender.php +++ b/engine/lib/extender.php @@ -136,14 +136,15 @@ function can_edit_extender($extender_id, $type, $user_guid = 0) { $functionname = "elgg_get_{$type}_from_id"; if (is_callable($functionname)) { - $extender = $functionname($extender_id); + $extender = call_user_func($functionname, $extender_id); } else { return false; } - if (!is_a($extender, "ElggExtender")) { + if (!($extender instanceof ElggExtender)) { return false; } + /* @var ElggExtender $extender */ // If the owner is the specified user, great! They can edit. if ($extender->getOwnerGUID() == $user->getGUID()) { @@ -175,7 +176,7 @@ function elgg_register_extender_url_handler($extender_type, $extender_name, $fun global $CONFIG; - if (!is_callable($function_name)) { + if (!is_callable($function_name, true)) { return false; } @@ -228,7 +229,7 @@ function get_extender_url(ElggExtender $extender) { if ($url == "") { $nameid = $extender->id; if ($type == 'volatile') { - $nameid == $extender->name; + $nameid = $extender->name; } $url = "export/$view/$guid/$type/$nameid/"; } diff --git a/engine/lib/group.php b/engine/lib/group.php index da5fb8656..624029d98 100644 --- a/engine/lib/group.php +++ b/engine/lib/group.php @@ -33,6 +33,7 @@ function get_group_entity_as_row($guid) { * @param string $description Description * * @return bool + * @access private */ function create_group_entity($guid, $name, $description) { global $CONFIG; @@ -247,48 +248,42 @@ function get_users_membership($user_guid) { } /** - * Checks access to a group. + * May the current user access item(s) on this page? If the page owner is a group, + * membership, visibility, and logged in status are taken into account. * * @param boolean $forward If set to true (default), will forward the page; * if set to false, will return true or false. * - * @return true|false If $forward is set to false. + * @return bool If $forward is set to false. */ function group_gatekeeper($forward = true) { - $allowed = true; - $url = ''; - - if ($group = elgg_get_page_owner_entity()) { - if ($group instanceof ElggGroup) { - $url = $group->getURL(); - if (!$group->isPublicMembership()) { - // closed group so must be member or an admin - - if (!elgg_is_logged_in()) { - $allowed = false; - if ($forward == true) { - $_SESSION['last_forward_from'] = current_page_url(); - register_error(elgg_echo('loggedinrequired')); - forward('', 'login'); - } - } else if (!$group->isMember(elgg_get_logged_in_user_entity())) { - $allowed = false; - } - // Admin override - if (elgg_is_admin_logged_in()) { - $allowed = true; - } - } - } + $page_owner_guid = elgg_get_page_owner_guid(); + if (!$page_owner_guid) { + return true; } + $visibility = ElggGroupItemVisibility::factory($page_owner_guid); - if ($forward && $allowed == false) { - register_error(elgg_echo('membershiprequired')); - forward($url, 'member'); + if (!$visibility->shouldHideItems) { + return true; + } + if ($forward) { + // only forward to group if user can see it + $group = get_entity($page_owner_guid); + $forward_url = $group ? $group->getURL() : ''; + + if (!elgg_is_logged_in()) { + $_SESSION['last_forward_from'] = current_page_url(); + $forward_reason = 'login'; + } else { + $forward_reason = 'member'; + } + + register_error(elgg_echo($visibility->reasonHidden)); + forward($forward_url, $forward_reason); } - return $allowed; + return false; } /** diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php index 77fa30e41..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'); } @@ -449,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 @@ -732,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 @@ -743,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(); } @@ -889,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); @@ -912,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 8c3952594..86624cd7c 100644 --- a/engine/lib/navigation.php +++ b/engine/lib/navigation.php @@ -308,6 +308,32 @@ function elgg_site_menu_setup($hook, $type, $return, $params) { $return['more'] = array_splice($return['default'], $max_display_items); } } + + // check if we have anything selected + $selected = false; + foreach ($return as $section_name => $section) { + foreach ($section as $key => $item) { + if ($item->getSelected()) { + $selected = true; + break 2; + } + } + } + + if (!$selected) { + // nothing selected, match name to context + foreach ($return as $section_name => $section) { + foreach ($section as $key => $item) { + // only highlight internal links + if (strpos($item->getHref(), elgg_get_site_url()) === 0) { + if ($item->getName() == elgg_get_context()) { + $return[$section_name][$key]->setSelected(true); + break 2; + } + } + } + } + } return $return; } diff --git a/engine/lib/notification.php b/engine/lib/notification.php index 20e32ae55..09ebc9541 100644 --- a/engine/lib/notification.php +++ b/engine/lib/notification.php @@ -38,7 +38,7 @@ $NOTIFICATION_HANDLERS = array(); function register_notification_handler($method, $handler, $params = NULL) { global $NOTIFICATION_HANDLERS; - if (is_callable($handler)) { + if (is_callable($handler, true)) { $NOTIFICATION_HANDLERS[$method] = new stdClass; $NOTIFICATION_HANDLERS[$method]->handler = $handler; @@ -131,8 +131,9 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth // Extract method details from list $details = $NOTIFICATION_HANDLERS[$method]; $handler = $details->handler; + /* @var callable $handler */ - if ((!$NOTIFICATION_HANDLERS[$method]) || (!$handler)) { + if ((!$NOTIFICATION_HANDLERS[$method]) || (!$handler) || (!is_callable($handler))) { error_log(elgg_echo('NotificationException:NoHandlerFound', array($method))); } @@ -140,7 +141,7 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth // Trigger handler and retrieve result. try { - $result[$guid][$method] = $handler( + $result[$guid][$method] = call_user_func($handler, $from ? get_entity($from) : NULL, // From entity get_entity($guid), // To entity $subject, // The subject diff --git a/engine/lib/objects.php b/engine/lib/objects.php index 1dd5c5ec7..852bdb793 100644 --- a/engine/lib/objects.php +++ b/engine/lib/objects.php @@ -31,6 +31,7 @@ function get_object_entity_as_row($guid) { * @param string $description The object's description * * @return bool + * @access private */ function create_object_entity($guid, $title, $description) { global $CONFIG; diff --git a/engine/lib/output.php b/engine/lib/output.php index 7bfc4be6e..9295f2173 100644 --- a/engine/lib/output.php +++ b/engine/lib/output.php @@ -16,7 +16,7 @@ **/ function parse_urls($text) { // @todo this causes problems with <attr = "val"> - // must be ing <attr="val"> format (no space). + // must be in <attr="val"> format (no space). // By default htmlawed rewrites tags to this format. // if PHP supported conditional negative lookbehinds we could use this: // $r = preg_replace_callback('/(?<!=)(?<![ ])?(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', @@ -43,51 +43,26 @@ function parse_urls($text) { /** * Create paragraphs from text with line spacing - * Borrowed from Wordpress. * * @param string $pee The string - * @param bool $br Add BRs? + * @deprecated Use elgg_autop instead + * @todo Add deprecation warning in 1.9 * - * @todo Rewrite * @return string **/ -function autop($pee, $br = 1) { - $pee = $pee . "\n"; // just to make things a little easier, pad the end - $pee = preg_replace('|<br />\s*<br />|', "\n\n", $pee); - // Space things out a little - $allblocks = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|map|area|blockquote|address|math|style|input|p|h[1-6]|hr)'; - $pee = preg_replace('!(<' . $allblocks . '[^>]*>)!', "\n$1", $pee); - $pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee); - $pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines - if (strpos($pee, '<object') !== false) { - $pee = preg_replace('|\s*<param([^>]*)>\s*|', "<param$1>", $pee); // no pee inside object/embed - $pee = preg_replace('|\s*</embed>\s*|', '</embed>', $pee); - } - $pee = preg_replace("/\n\n+/", "\n\n", $pee); // take care of duplicates - $pee = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "<p>$1</p>\n", $pee); // make paragraphs, including one at the end - $pee = preg_replace('|<p>\s*?</p>|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace - $pee = preg_replace('!<p>([^<]+)\s*?(</(?:div|address|form)[^>]*>)!', "<p>$1</p>$2", $pee); - $pee = preg_replace('|<p>|', "$1<p>", $pee); - $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); // don't pee all over a tag - $pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // problem with nested lists - $pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee); - $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee); - $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee); - $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); - if ($br) { - $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', create_function('$matches', 'return str_replace("\n", "<WPPreserveNewline />", $matches[0]);'), $pee); - $pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee); // optionally make line breaks - $pee = str_replace('<WPPreserveNewline />', "\n", $pee); - } - $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', "$1", $pee); - $pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee); - //if (strpos($pee, '<pre') !== false) { - // mind the space between the ? and >. Only there because of the comment. - // $pee = preg_replace_callback('!(<pre.*? >)(.*?)</pre>!is', 'clean_pre', $pee ); - //} - $pee = preg_replace("|\n</p>$|", '</p>', $pee); - - return $pee; +function autop($pee) { + return elgg_autop($pee); +} + +/** + * Create paragraphs from text with line spacing + * + * @param string $string The string + * + * @return string + **/ +function elgg_autop($string) { + return ElggAutoP::getInstance()->process($string); } /** @@ -271,8 +246,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; @@ -312,6 +287,8 @@ function elgg_get_friendly_title($title) { // handle some special cases $title = str_replace('&', 'and', $title); + // quotes and angle brackets stored in the database as html encoded + $title = htmlspecialchars_decode($title); $title = ElggTranslit::urlize($title); @@ -384,7 +361,7 @@ function elgg_get_friendly_time($time) { /** * Strip tags and offer plugins the chance. * Plugins register for output:strip_tags plugin hook. - * Original string included in $params['original_string'] + * Original string included in $params['original_string'] * * @param string $string Formatted string * @@ -398,3 +375,74 @@ function elgg_strip_tags($string) { return $string; } + +/** + * Apply html_entity_decode() to a string while re-entitising HTML + * special char entities to prevent them from being decoded back to their + * unsafe original forms. + * + * This relies on html_entity_decode() not translating entities when + * doing so leaves behind another entity, e.g. &gt; if decoded would + * create > which is another entity itself. This seems to escape the + * usual behaviour where any two paired entities creating a HTML tag are + * usually decoded, i.e. a lone > is not decoded, but <foo> would + * be decoded to <foo> since it creates a full tag. + * + * Note: This function is poorly explained in the manual - which is really + * bad given its potential for misuse on user input already escaped elsewhere. + * Stackoverflow is littered with advice to use this function in the precise + * way that would lead to user input being capable of injecting arbitrary HTML. + * + * @param string $string + * + * @return string + * + * @author Pádraic Brady + * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com) + * @license Released under dual-license GPL2/MIT by explicit permission of Pádraic Brady + * + * @access private + */ +function _elgg_html_decode($string) { + $string = str_replace( + array('>', '<', '&', '"', '''), + array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), + $string + ); + $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8'); + $string = str_replace( + array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), + array('>', '<', '&', '"', '''), + $string + ); + return $string; +} + +/** + * Unit tests for Output + * + * @param sting $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params + * + * @return array + * @access private + */ +function output_unit_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/output.php'; + return $value; +} + +/** + * Initialise the Output subsystem. + * + * @return void + * @access private + */ +function output_init() { + elgg_register_plugin_hook_handler('unit_test', 'system', 'output_unit_test'); +} + +elgg_register_event_handler('init', 'system', 'output_init'); diff --git a/engine/lib/pagehandler.php b/engine/lib/pagehandler.php index ba7518a77..0cf99b6fe 100644 --- a/engine/lib/pagehandler.php +++ b/engine/lib/pagehandler.php @@ -45,7 +45,10 @@ function page_handler($handler, $page) { $page = $request['segments']; $result = false; - if (isset($CONFIG->pagehandler) && !empty($handler) && isset($CONFIG->pagehandler[$handler])) { + if (isset($CONFIG->pagehandler) + && !empty($handler) + && isset($CONFIG->pagehandler[$handler]) + && is_callable($CONFIG->pagehandler[$handler])) { $function = $CONFIG->pagehandler[$handler]; $result = call_user_func($function, $page, $handler); } @@ -76,14 +79,15 @@ function page_handler($handler, $page) { * @param string $handler The page type to handle * @param string $function Your function name * - * @return true|false Depending on success + * @return bool Depending on success */ function elgg_register_page_handler($handler, $function) { global $CONFIG; + if (!isset($CONFIG->pagehandler)) { $CONFIG->pagehandler = array(); } - if (is_callable($function)) { + if (is_callable($function, true)) { $CONFIG->pagehandler[$handler] = $function; return true; } diff --git a/engine/lib/pageowner.php b/engine/lib/pageowner.php index 0cf0e0625..94765feee 100644 --- a/engine/lib/pageowner.php +++ b/engine/lib/pageowner.php @@ -37,6 +37,8 @@ function elgg_get_page_owner_guid($guid = 0) { /** * Gets the owner entity for the current page. * + * @note Access is disabled when getting the page owner entity. + * * @return ElggEntity|false The current page owner or false if none. * * @since 1.8.0 @@ -44,10 +46,14 @@ function elgg_get_page_owner_guid($guid = 0) { function elgg_get_page_owner_entity() { $guid = elgg_get_page_owner_guid(); if ($guid > 0) { - return get_entity($guid); + $ia = elgg_set_ignore_access(true); + $owner = get_entity($guid); + elgg_set_ignore_access($ia); + + return $owner; } - return FALSE; + return false; } /** @@ -75,6 +81,8 @@ function elgg_set_page_owner_guid($guid) { * <handler>/edit/<entity guid> * <handler>/group/<group guid> * + * @note Access is disabled while finding the page owner for the group gatekeeper functions. + * * * @param string $hook 'page_owner' * @param string $entity_type 'system' @@ -90,6 +98,8 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) return $returnvalue; } + $ia = elgg_set_ignore_access(true); + $username = get_input("username"); if ($username) { // @todo using a username of group:<guid> is deprecated @@ -97,6 +107,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) preg_match('/group\:([0-9]+)/i', $username, $matches); $guid = $matches[1]; if ($entity = get_entity($guid)) { + elgg_set_ignore_access($ia); return $entity->getGUID(); } } @@ -109,6 +120,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) $owner = get_input("owner_guid"); if ($owner) { if ($user = get_entity($owner)) { + elgg_set_ignore_access($ia); return $user->getGUID(); } } @@ -130,6 +142,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) case 'friends': $user = get_user_by_username($segments[2]); if ($user) { + elgg_set_ignore_access($ia); return $user->getGUID(); } break; @@ -137,6 +150,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) case 'edit': $entity = get_entity($segments[2]); if ($entity) { + elgg_set_ignore_access($ia); return $entity->getContainerGUID(); } break; @@ -144,6 +158,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) case 'group': $entity = get_entity($segments[2]); if ($entity) { + elgg_set_ignore_access($ia); return $entity->getGUID(); } break; @@ -151,7 +166,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) } } - return $returnvalue; + elgg_set_ignore_access($ia); } /** diff --git a/engine/lib/pam.php b/engine/lib/pam.php index 4f9f44278..1c9c3bfe1 100644 --- a/engine/lib/pam.php +++ b/engine/lib/pam.php @@ -30,7 +30,9 @@ $_PAM_HANDLERS = array(); * failure, return false or throw an exception. Returning nothing indicates that * the handler wants to be skipped. * - * @param string $handler The handler function in the format + * Note, $handler must be string callback (not an array/Closure). + * + * @param string $handler Callable global handler function in the format () * pam_handler($credentials = NULL); * @param string $importance The importance - "sufficient" (default) or "required" * @param string $policy The policy type, default is "user" @@ -45,7 +47,8 @@ function register_pam_handler($handler, $importance = "sufficient", $policy = "u $_PAM_HANDLERS[$policy] = array(); } - if (is_callable($handler)) { + // @todo remove requirement that $handle be a global function + if (is_string($handler) && is_callable($handler, true)) { $_PAM_HANDLERS[$policy][$handler] = new stdClass; $_PAM_HANDLERS[$policy][$handler]->handler = $handler; diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php index d5cd4fe76..94aff277e 100644 --- a/engine/lib/plugins.php +++ b/engine/lib/plugins.php @@ -176,6 +176,19 @@ function elgg_generate_plugin_entities() { } /** + * Cache a reference to this plugin by its ID + * + * @param ElggPlugin $plugin + * + * @access private + */ +function _elgg_cache_plugin_by_id(ElggPlugin $plugin) { + $map = (array) elgg_get_config('plugins_by_id_map'); + $map[$plugin->getID()] = $plugin; + elgg_set_config('plugins_by_id_map', $map); +} + +/** * Returns an ElggPlugin object with the path $path. * * @param string $plugin_id The id (dir name) of the plugin. NOT the guid. @@ -183,6 +196,11 @@ function elgg_generate_plugin_entities() { * @since 1.8.0 */ function elgg_get_plugin_from_id($plugin_id) { + $map = (array) elgg_get_config('plugins_by_id_map'); + if (isset($map[$plugin_id])) { + return $map[$plugin_id]; + } + $plugin_id = sanitize_string($plugin_id); $db_prefix = get_config('dbprefix'); @@ -190,6 +208,7 @@ function elgg_get_plugin_from_id($plugin_id) { 'type' => 'object', 'subtype' => 'plugin', 'joins' => array("JOIN {$db_prefix}objects_entity oe on oe.guid = e.guid"), + 'selects' => array("oe.title", "oe.description"), 'wheres' => array("oe.title = '$plugin_id'"), 'limit' => 1 ); @@ -512,6 +531,8 @@ function elgg_namespace_plugin_private_setting($type, $name, $id = null) { * @return string|false Plugin name, or false if no plugin name was called * @since 1.8.0 * @access private + * + * @todo get rid of this */ function elgg_get_calling_plugin_id($mainfilename = false) { if (!$mainfilename) { @@ -920,6 +941,7 @@ function elgg_set_plugin_setting($name, $value, $plugin_id = null) { * * @return mixed * @since 1.8.0 + * @todo make $plugin_id required in future version */ function elgg_get_plugin_setting($name, $plugin_id = null) { if ($plugin_id) { diff --git a/engine/lib/private_settings.php b/engine/lib/private_settings.php index 1fa9bdb66..7541f7b3b 100644 --- a/engine/lib/private_settings.php +++ b/engine/lib/private_settings.php @@ -349,11 +349,6 @@ function set_private_setting($entity_guid, $name, $value) { $name = sanitise_string($name); $value = sanitise_string($value); - $entity = get_entity($entity_guid); - if (!$entity instanceof ElggEntity) { - return false; - } - $result = insert_data("INSERT into {$CONFIG->dbprefix}private_settings (entity_guid, name, value) VALUES ($entity_guid, '$name', '$value') diff --git a/engine/lib/relationships.php b/engine/lib/relationships.php index 7b42ed5be..41bfa4e73 100644 --- a/engine/lib/relationships.php +++ b/engine/lib/relationships.php @@ -416,7 +416,7 @@ function elgg_list_entities_from_relationship_count($options) { function elgg_register_relationship_url_handler($relationship_type, $function_name) { global $CONFIG; - if (!is_callable($function_name)) { + if (!is_callable($function_name, true)) { return false; } diff --git a/engine/lib/river.php b/engine/lib/river.php index b717a7756..33f34360e 100644 --- a/engine/lib/river.php +++ b/engine/lib/river.php @@ -361,6 +361,7 @@ function elgg_get_river(array $options = array()) { } $river_items = get_data($query, 'elgg_row_to_elgg_river_item'); + _elgg_prefetch_river_entities($river_items); return $river_items; } else { @@ -370,11 +371,56 @@ function elgg_get_river(array $options = array()) { } /** + * Prefetch entities that will be displayed in the river. + * + * @param ElggRiverItem[] $river_items + * @access private + */ +function _elgg_prefetch_river_entities(array $river_items) { + // prefetch objects and subjects + $guids = array(); + foreach ($river_items as $item) { + if ($item->subject_guid && !retrieve_cached_entity($item->subject_guid)) { + $guids[$item->subject_guid] = true; + } + if ($item->object_guid && !retrieve_cached_entity($item->object_guid)) { + $guids[$item->object_guid] = true; + } + } + if ($guids) { + // avoid creating oversized query + // @todo how to better handle this? + $guids = array_slice($guids, 0, 300, true); + // return value unneeded, just priming cache + elgg_get_entities(array( + 'guids' => array_keys($guids), + 'limit' => 0, + )); + } + + // prefetch object containers + $guids = array(); + foreach ($river_items as $item) { + $object = $item->getObjectEntity(); + if ($object->container_guid && !retrieve_cached_entity($object->container_guid)) { + $guids[$object->container_guid] = true; + } + } + if ($guids) { + $guids = array_slice($guids, 0, 300, true); + elgg_get_entities(array( + 'guids' => array_keys($guids), + 'limit' => 0, + )); + } +} + +/** * List river items * * @param array $options Any options from elgg_get_river() plus: * pagination => BOOL Display pagination links (true) - + * * @return string * @since 1.8.0 */ diff --git a/engine/lib/sites.php b/engine/lib/sites.php index 236fbc28e..5a390167d 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; } /** @@ -50,6 +58,7 @@ function get_site_entity_as_row($guid) { * @param string $url URL of the site * * @return bool + * @access private */ function create_site_entity($guid, $name, $description, $url) { global $CONFIG; diff --git a/engine/lib/upgrade.php b/engine/lib/upgrade.php index f0874a483..f4f4b16f5 100644 --- a/engine/lib/upgrade.php +++ b/engine/lib/upgrade.php @@ -311,3 +311,58 @@ function elgg_upgrade_bootstrap_17_to_18() { return elgg_set_processed_upgrades($processed_upgrades); } + +/** + * Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades. + * + * @see _elgg_upgrade_lock() + * + * @return bool + * @access private + */ +function _elgg_upgrade_lock() { + global $CONFIG; + + if (!_elgg_upgrade_is_locked()) { + // lock it + insert_data("create table {$CONFIG->dbprefix}upgrade_lock (id INT)"); + elgg_log('Locked for upgrade.', 'NOTICE'); + return true; + } + + elgg_log('Cannot lock for upgrade: already locked.', 'WARNING'); + return false; +} + +/** + * Unlocks upgrade. + * + * @see _elgg_upgrade_lock() + * + * @access private + */ +function _elgg_upgrade_unlock() { + global $CONFIG; + delete_data("drop table {$CONFIG->dbprefix}upgrade_lock"); + elgg_log('Upgrade unlocked.', 'NOTICE'); +} + +/** + * Checks if upgrade is locked + * + * @return bool + * @access private + */ +function _elgg_upgrade_is_locked() { + global $CONFIG, $DB_QUERY_CACHE; + + $is_locked = count(get_data("show tables like '{$CONFIG->dbprefix}upgrade_lock'")); + + // Invalidate query cache + if ($DB_QUERY_CACHE) { + $DB_QUERY_CACHE->clear(); + elgg_log("Query cache invalidated", 'NOTICE'); + } + + return $is_locked; +} diff --git a/engine/lib/upgrades/2010052601.php b/engine/lib/upgrades/2010052601.php index 5b477910f..a9cca6dc5 100644 --- a/engine/lib/upgrades/2010052601.php +++ b/engine/lib/upgrades/2010052601.php @@ -9,14 +9,14 @@ $params = array('type' => 'group', $groups = elgg_get_entities($params); if ($groups) { foreach ($groups as $group) { - $group->name = html_entity_decode($group->name, ENT_COMPAT, 'UTF-8'); - $group->description = html_entity_decode($group->description, ENT_COMPAT, 'UTF-8'); - $group->briefdescription = html_entity_decode($group->briefdescription, ENT_COMPAT, 'UTF-8'); - $group->website = html_entity_decode($group->website, ENT_COMPAT, 'UTF-8'); + $group->name = _elgg_html_decode($group->name); + $group->description = _elgg_html_decode($group->description); + $group->briefdescription = _elgg_html_decode($group->briefdescription); + $group->website = _elgg_html_decode($group->website); if ($group->interests) { $tags = $group->interests; - foreach ($tags as $index=>$tag) { - $tags[$index] = html_entity_decode($tag, ENT_COMPAT, 'UTF-8'); + foreach ($tags as $index => $tag) { + $tags[$index] = _elgg_html_decode($tag); } $group->interests = $tags; } diff --git a/engine/lib/upgrades/2011010101.php b/engine/lib/upgrades/2011010101.php index a1ee92622..f4411ee20 100644 --- a/engine/lib/upgrades/2011010101.php +++ b/engine/lib/upgrades/2011010101.php @@ -93,4 +93,6 @@ $processed_upgrades[] = '2011010101.php'; $processed_upgrades = array_unique($processed_upgrades); elgg_set_processed_upgrades($processed_upgrades); +_elgg_upgrade_unlock(); + forward('upgrade.php'); diff --git a/engine/lib/users.php b/engine/lib/users.php index 6b0b1429b..9d1fba2d4 100644 --- a/engine/lib/users.php +++ b/engine/lib/users.php @@ -44,6 +44,7 @@ function get_user_entity_as_row($guid) { * @param string $code A code * * @return bool + * @access private */ function create_user_entity($guid, $name, $username, $password, $salt, $email, $language, $code) { global $CONFIG; diff --git a/engine/lib/views.php b/engine/lib/views.php index b00334062..8618c2997 100644 --- a/engine/lib/views.php +++ b/engine/lib/views.php @@ -101,15 +101,15 @@ function elgg_get_viewtype() { return $CURRENT_SYSTEM_VIEWTYPE; } - $viewtype = get_input('view', NULL); - if ($viewtype) { + $viewtype = get_input('view', '', false); + if (is_string($viewtype) && $viewtype !== '') { // only word characters allowed. - if (!preg_match('[\W]', $viewtype)) { + if (!preg_match('/\W/', $viewtype)) { return $viewtype; } } - if (isset($CONFIG->view) && !empty($CONFIG->view)) { + if (!empty($CONFIG->view)) { return $CONFIG->view; } @@ -258,8 +258,6 @@ function elgg_get_view_location($view, $viewtype = '') { } else { return $CONFIG->views->locations[$viewtype][$view]; } - - return false; } /** @@ -329,7 +327,7 @@ function elgg_view_exists($view, $viewtype = '', $recurse = true) { $location = $CONFIG->views->locations[$viewtype][$view]; } - if (file_exists($location . "{$viewtype}/{$view}.php")) { + if (file_exists("{$location}{$viewtype}/{$view}.php")) { return true; } @@ -378,7 +376,7 @@ function elgg_view_exists($view, $viewtype = '', $recurse = true) { * @param boolean $bypass If set to true, elgg_view will bypass any specified * alternative template handler; by default, it will * hand off to this if requested (see set_template_handler) - * @param boolean $debug If set to true, the viewer will complain if it can't find a view + * @param boolean $ignored This argument is ignored and will be removed eventually * @param string $viewtype If set, forces the viewtype for the elgg_view call to be * this value (default: standard detection) * @@ -386,18 +384,30 @@ function elgg_view_exists($view, $viewtype = '', $recurse = true) { * @see set_template_handler() * @example views/elgg_view.php * @link http://docs.elgg.org/View - * @todo $debug isn't used. - * @todo $usercache is redundant. */ -function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $viewtype = '') { +function elgg_view($view, $vars = array(), $bypass = false, $ignored = false, $viewtype = '') { global $CONFIG; - static $usercache; - - $view = (string)$view; + if (!is_string($view) || !is_string($viewtype)) { + elgg_log("View and Viewtype in views must be a strings: $view", 'NOTICE'); + return ''; + } // basic checking for bad paths if (strpos($view, '..') !== false) { - return false; + return ''; + } + + if (!is_array($vars)) { + elgg_log("Vars in views must be an array: $view", 'ERROR'); + $vars = array(); + } + + // Get the current viewtype + if ($viewtype === '') { + $viewtype = elgg_get_viewtype(); + } elseif (preg_match('/\W/', $viewtype)) { + // Viewtypes can only be alphanumeric + return ''; } $view_orig = $view; @@ -408,19 +418,6 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie elgg_trigger_event('pagesetup', 'system'); } - if (!is_array($usercache)) { - $usercache = array(); - } - - if (!is_array($vars)) { - elgg_log("Vars in views must be an array: $view", 'ERROR'); - $vars = array(); - } - - if (empty($vars)) { - $vars = array(); - } - // @warning - plugin authors: do not expect user, config, and url to be // set by elgg_view() in the future. Instead, use elgg_get_logged_in_user_entity(), // elgg_get_config(), and elgg_get_site_url() in your views. @@ -475,16 +472,6 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie } } - // Get the current viewtype - if (empty($viewtype)) { - $viewtype = elgg_get_viewtype(); - } - - // Viewtypes can only be alphanumeric - if (preg_match('[\W]', $viewtype)) { - return ''; - } - // Set up any extensions to the requested view if (isset($CONFIG->views->extensions[$view])) { $viewlist = $CONFIG->views->extensions[$view]; @@ -496,19 +483,21 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie ob_start(); foreach ($viewlist as $priority => $view) { + $view_location = elgg_get_view_location($view, $viewtype); $view_file = "$view_location$viewtype/$view.php"; - $default_location = elgg_get_view_location($view, 'default'); - $default_view_file = "{$default_location}default/$view.php"; - // try to include view if (!file_exists($view_file) || !include($view_file)) { // requested view does not exist $error = "$viewtype/$view view does not exist."; // attempt to load default view - if ($viewtype != 'default' && elgg_does_viewtype_fallback($viewtype)) { + if ($viewtype !== 'default' && elgg_does_viewtype_fallback($viewtype)) { + + $default_location = elgg_get_view_location($view, 'default'); + $default_view_file = "{$default_location}default/$view.php"; + if (file_exists($default_view_file) && include($default_view_file)) { // default view found $error .= " Using default/$view instead."; @@ -533,7 +522,7 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie // backward compatibility with less granular hook will be gone in 2.0 $content_tmp = elgg_trigger_plugin_hook('display', 'view', $params, $content); - if ($content_tmp != $content) { + if ($content_tmp !== $content) { $content = $content_tmp; elgg_deprecated_notice('The display:view plugin hook is deprecated by view:view_name', 1.8); } @@ -559,33 +548,32 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie * @param string $view_extension This view is added to $view * @param int $priority The priority, from 0 to 1000, * to add at (lowest numbers displayed first) - * @param string $viewtype Not used * * @return void * @since 1.7.0 * @link http://docs.elgg.org/Views/Extend * @example views/extend.php */ -function elgg_extend_view($view, $view_extension, $priority = 501, $viewtype = '') { +function elgg_extend_view($view, $view_extension, $priority = 501) { global $CONFIG; if (!isset($CONFIG->views)) { - $CONFIG->views = new stdClass; - } - - if (!isset($CONFIG->views->extensions)) { - $CONFIG->views->extensions = array(); - } - - if (!isset($CONFIG->views->extensions[$view])) { - $CONFIG->views->extensions[$view][500] = "{$view}"; + $CONFIG->views = (object) array( + 'extensions' => array(), + ); + $CONFIG->views->extensions[$view][500] = (string)$view; + } else { + if (!isset($CONFIG->views->extensions[$view])) { + $CONFIG->views->extensions[$view][500] = (string)$view; + } } + // raise priority until it doesn't match one already registered while (isset($CONFIG->views->extensions[$view][$priority])) { $priority++; } - $CONFIG->views->extensions[$view][$priority] = "{$view_extension}"; + $CONFIG->views->extensions[$view][$priority] = (string)$view_extension; ksort($CONFIG->views->extensions[$view]); } @@ -601,14 +589,6 @@ function elgg_extend_view($view, $view_extension, $priority = 501, $viewtype = ' function elgg_unextend_view($view, $view_extension) { global $CONFIG; - if (!isset($CONFIG->views)) { - return FALSE; - } - - if (!isset($CONFIG->views->extensions)) { - return FALSE; - } - if (!isset($CONFIG->views->extensions[$view])) { return FALSE; } @@ -1105,10 +1085,6 @@ function elgg_view_annotation_list($annotations, array $vars = array()) { * @todo Change the hook name. */ function elgg_view_entity_annotations(ElggEntity $entity, $full_view = true) { - if (!$entity) { - return false; - } - if (!($entity instanceof ElggEntity)) { return false; } @@ -1131,7 +1107,7 @@ function elgg_view_entity_annotations(ElggEntity $entity, $full_view = true) { * This is a shortcut for {@elgg_view page/elements/title}. * * @param string $title The page title - * @param string $vars View variables (was submenu be displayed? (deprecated)) + * @param array $vars View variables (was submenu be displayed? (deprecated)) * * @return string The HTML (etc) */ @@ -1203,7 +1179,7 @@ function elgg_view_comments($entity, $add_comment = true, array $vars = array()) * * @param string $image The icon and other information * @param string $body Description content - * @param string $vars Additional parameters for the view + * @param array $vars Additional parameters for the view * * @return string * @since 1.8.0 @@ -1230,7 +1206,6 @@ function elgg_view_image_block($image, $body, $vars = array()) { * @since 1.8.0 */ function elgg_view_module($type, $title, $body, array $vars = array()) { - $vars['class'] = elgg_extract('class', $vars, '') . " elgg-module-$type"; $vars['title'] = $title; $vars['body'] = $body; @@ -1243,11 +1218,15 @@ function elgg_view_module($type, $title, $body, array $vars = array()) { * @param ElggRiverItem $item A river item object * @param array $vars An array of variables for the view * - * @return string|false Depending on success + * @return string returns empty string if could not be rendered */ function elgg_view_river_item($item, array $vars = array()) { + if (!($item instanceof ElggRiverItem)) { + return ''; + } // checking default viewtype since some viewtypes do not have unique views per item (rss) - if (!$item || !$item->getView() || !elgg_view_exists($item->getView(), 'default')) { + $view = $item->getView(); + if (!$view || !elgg_view_exists($view, 'default')) { return ''; } @@ -1257,6 +1236,15 @@ function elgg_view_river_item($item, array $vars = array()) { // subject is disabled or subject/object deleted return ''; } + // Don't hide objects in closed groups that a user can see. + // see http://trac.elgg.org/ticket/4789 +// else { +// // hide based on object's container +// $visibility = ElggGroupItemVisibility::factory($object->container_guid); +// if ($visibility->shouldHideItems) { +// return ''; +// } +// } $vars['item'] = $item; @@ -1339,7 +1327,7 @@ function elgg_view_list_item($item, array $vars = array()) { return elgg_view_river_item($item, $vars); } - return false; + return ''; } /** @@ -1354,7 +1342,7 @@ function elgg_view_list_item($item, array $vars = array()) { */ function elgg_view_icon($name, $class = '') { // @todo deprecate boolean in Elgg 1.9 - if (is_bool($class) && $class === true) { + if ($class === true) { $class = 'float'; } return "<span class=\"elgg-icon elgg-icon-$name $class\"></span>"; @@ -1403,7 +1391,8 @@ function elgg_view_access_collections($owner_guid) { */ function set_template_handler($function_name) { global $CONFIG; - if (!empty($function_name) && is_callable($function_name)) { + + if (is_callable($function_name)) { $CONFIG->template_handler = $function_name; return true; } @@ -1516,17 +1505,13 @@ function elgg_view_tree($view_root, $viewtype = "") { * @param string $base_location_path The base views directory to use with elgg_set_view_location() * @param string $viewtype The type of view we're looking at (default, rss, etc) * - * @return void + * @return bool returns false if folder can't be read * @since 1.7.0 * @see elgg_set_view_location() * @todo This seems overly complicated. * @access private */ function autoregister_views($view_base, $folder, $base_location_path, $viewtype) { - if (!isset($i)) { - $i = 0; - } - if ($handle = opendir($folder)) { while ($view = readdir($handle)) { if (!in_array($view, array('.', '..', '.svn', 'CVS')) && !is_dir($folder . "/" . $view)) { @@ -1608,16 +1593,15 @@ function elgg_views_handle_deprecated_views() { function elgg_views_boot() { global $CONFIG; - elgg_register_simplecache_view('css/elgg'); elgg_register_simplecache_view('css/ie'); elgg_register_simplecache_view('css/ie6'); elgg_register_simplecache_view('css/ie7'); - elgg_register_simplecache_view('js/elgg'); elgg_register_js('jquery', '/vendors/jquery/jquery-1.6.4.min.js', 'head'); elgg_register_js('jquery-ui', '/vendors/jquery/jquery-ui-1.8.16.min.js', 'head'); elgg_register_js('jquery.form', '/vendors/jquery/jquery.form.js'); - + + elgg_register_simplecache_view('js/elgg'); $elgg_js_url = elgg_get_simplecache_url('js', 'elgg'); elgg_register_js('elgg', $elgg_js_url, 'head'); @@ -1626,14 +1610,17 @@ function elgg_views_boot() { elgg_load_js('elgg'); elgg_register_simplecache_view('js/lightbox'); - elgg_register_simplecache_view('css/lightbox'); $lightbox_js_url = elgg_get_simplecache_url('js', 'lightbox'); elgg_register_js('lightbox', $lightbox_js_url); + + elgg_register_simplecache_view('css/lightbox'); $lightbox_css_url = elgg_get_simplecache_url('css', 'lightbox'); elgg_register_css('lightbox', $lightbox_css_url); + elgg_register_simplecache_view('css/elgg'); $elgg_css_url = elgg_get_simplecache_url('css', 'elgg'); elgg_register_css('elgg', $elgg_css_url); + elgg_load_css('elgg'); elgg_register_ajax_view('js/languages'); @@ -1647,13 +1634,13 @@ function elgg_views_boot() { $views = scandir($view_path); foreach ($views as $view) { - if ('.' !== substr($view, 0, 1) && is_dir($view_path . $view)) { + if ($view[0] !== '.' && is_dir($view_path . $view)) { elgg_register_viewtype($view); } } // 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/lib/web_services.php b/engine/lib/web_services.php index da3ed76a9..b6289184a 100644 --- a/engine/lib/web_services.php +++ b/engine/lib/web_services.php @@ -178,7 +178,7 @@ function authenticate_method($method) { // check if user authentication is required if ($API_METHODS[$method]["require_user_auth"] == true) { if ($user_auth_result == false) { - throw new APIException($user_pam->getFailureMessage()); + throw new APIException($user_pam->getFailureMessage(), ErrorResult::$RESULT_FAIL_AUTHTOKEN); } } @@ -232,6 +232,7 @@ function execute_method($method) { $function = $API_METHODS[$method]["function"]; $serialised_parameters = trim($serialised_parameters, ", "); + // @todo document why we cannot use call_user_func_array here $result = eval("return $function($serialised_parameters);"); // Sanity check result @@ -1194,6 +1195,8 @@ $ERRORS = array(); * * @return void * @access private + * + * @throws Exception */ function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { global $ERRORS; @@ -1265,10 +1268,10 @@ function service_handler($handler, $request) { // after the handler, the first identifier is response format // ex) http://example.org/services/api/rest/xml/?method=test - $reponse_format = array_shift($request); + $response_format = array_shift($request); // Which view - xml, json, ... - if ($reponse_format) { - elgg_set_viewtype($reponse_format); + if ($response_format && elgg_is_valid_view_type($response_format)) { + elgg_set_viewtype($response_format); } else { // default to xml elgg_set_viewtype("xml"); @@ -1278,11 +1281,9 @@ function service_handler($handler, $request) { // no handlers set or bad url header("HTTP/1.0 404 Not Found"); exit; - } else if (isset($CONFIG->servicehandler[$handler]) - && is_callable($CONFIG->servicehandler[$handler])) { - + } else if (isset($CONFIG->servicehandler[$handler]) && is_callable($CONFIG->servicehandler[$handler])) { $function = $CONFIG->servicehandler[$handler]; - $function($request, $handler); + call_user_func($function, $request, $handler); } else { // no handler for this web service header("HTTP/1.0 404 Not Found"); @@ -1301,10 +1302,11 @@ function service_handler($handler, $request) { */ function register_service_handler($handler, $function) { global $CONFIG; + if (!isset($CONFIG->servicehandler)) { $CONFIG->servicehandler = array(); } - if (is_callable($function)) { + if (is_callable($function, true)) { $CONFIG->servicehandler[$handler] = $function; return true; } @@ -1319,11 +1321,13 @@ function register_service_handler($handler, $function) { * * @param string $handler web services type * - * @return 1.7.0 + * @return void + * @since 1.7.0 */ function unregister_service_handler($handler) { global $CONFIG; - if (isset($CONFIG->servicehandler) && isset($CONFIG->servicehandler[$handler])) { + + if (isset($CONFIG->servicehandler, $CONFIG->servicehandler[$handler])) { unset($CONFIG->servicehandler[$handler]); } } @@ -1333,6 +1337,8 @@ function unregister_service_handler($handler) { * * @return void * @access private + * + * @throws SecurityException|APIException */ function rest_handler() { global $CONFIG; @@ -1387,7 +1393,7 @@ function rest_handler() { /** * Unit tests for API * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params @@ -1397,6 +1403,7 @@ function rest_handler() { */ function api_unit_test($hook, $type, $value, $params) { global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/services/api.php'; return $value; } @@ -1418,15 +1425,18 @@ function api_init() { elgg_echo("system.api.list"), "GET", false, false); // The authentication token api - expose_function("auth.gettoken", - "auth_gettoken", array( - 'username' => array ('type' => 'string'), - 'password' => array ('type' => 'string'), - ), - elgg_echo('auth.gettoken'), - 'POST', - false, - false); + expose_function( + "auth.gettoken", + "auth_gettoken", + array( + 'username' => array ('type' => 'string'), + 'password' => array ('type' => 'string'), + ), + elgg_echo('auth.gettoken'), + 'POST', + false, + false + ); } diff --git a/engine/lib/xml.php b/engine/lib/xml.php index 813bc4ee0..ff82d7e8a 100644 --- a/engine/lib/xml.php +++ b/engine/lib/xml.php @@ -101,47 +101,11 @@ function serialise_array_to_xml(array $data, $n = 0) { /** * Parse an XML file into an object. - * Based on code from http://de.php.net/manual/en/function.xml-parse-into-struct.php by - * efredricksen at gmail dot com * * @param string $xml The XML * * @return object */ function xml_to_object($xml) { - $parser = xml_parser_create(); - - // Parse $xml into a structure - xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); - xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); - xml_parse_into_struct($parser, $xml, $tags); - - xml_parser_free($parser); - - $elements = array(); - $stack = array(); - - foreach ($tags as $tag) { - $index = count($elements); - - if ($tag['type'] == "complete" || $tag['type'] == "open") { - $elements[$index] = new XmlElement; - $elements[$index]->name = $tag['tag']; - $elements[$index]->attributes = elgg_extract('attributes', $tag, ''); - $elements[$index]->content = elgg_extract('value', $tag, ''); - - if ($tag['type'] == "open") { - $elements[$index]->children = array(); - $stack[count($stack)] = &$elements; - $elements = &$elements[$index]->children; - } - } - - if ($tag['type'] == "close") { - $elements = &$stack[count($stack) - 1]; - unset($stack[count($stack) - 1]); - } - } - - return $elements[0]; + return new ElggXMLElement($xml); } |