diff options
Diffstat (limited to 'engine/lib')
61 files changed, 1820 insertions, 958 deletions
diff --git a/engine/lib/access.php b/engine/lib/access.php index e8b3b0d52..de0693ea8 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); } @@ -982,6 +1015,10 @@ function access_init() { * * Returns true to override the access system or null if no change is needed. * + * @param string $hook + * @param string $type + * @param bool $value + * @param array $params * @return true|null * @access private */ @@ -1014,10 +1051,18 @@ function elgg_override_permissions($hook, $type, $value, $params) { /** * Runs unit tests for the entities object. + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array + * * @access private */ function access_test($hook, $type, $value, $params) { global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/access_collections.php'; return $value; } diff --git a/engine/lib/actions.php b/engine/lib/actions.php index 53b185dea..8047914ac 100644 --- a/engine/lib/actions.php +++ b/engine/lib/actions.php @@ -65,18 +65,16 @@ function action($action, $forwarder = "") { // @todo REMOVE THESE ONCE #1509 IS IN PLACE. // Allow users to disable plugins without a token in order to // remove plugins that are incompatible. - // Login and logout are for convenience. + // Logout for convenience. // file/download (see #2010) $exceptions = array( 'admin/plugins/disable', 'logout', - 'login', 'file/download', ); if (!in_array($action, $exceptions)) { - // All actions require a token. - action_gatekeeper(); + action_gatekeeper($action); } $forwarder = str_replace(elgg_get_site_url(), "", $forwarder); @@ -189,6 +187,26 @@ function elgg_unregister_action($action) { } /** + * Is the token timestamp within acceptable range? + * + * @param int $ts timestamp from the CSRF token + * + * @return bool + */ +function _elgg_validate_token_timestamp($ts) { + $action_token_timeout = elgg_get_config('action_token_timeout'); + // default is 2 hours + $timeout = ($action_token_timeout !== null) ? $action_token_timeout : 2; + + $hour = 60 * 60; + $timeout = $timeout * $hour; + $now = time(); + + // Validate time to ensure its not crazy + return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout)); +} + +/** * Validate an action token. * * Calls to actions will automatically validate tokens. If tokens are not @@ -206,8 +224,6 @@ function elgg_unregister_action($action) { * @access private */ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) { - global $CONFIG; - if (!$token) { $token = get_input('__elgg_token'); } @@ -216,29 +232,18 @@ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) $ts = get_input('__elgg_ts'); } - if (!isset($CONFIG->action_token_timeout)) { - // default to 2 hours - $timeout = 2; - } else { - $timeout = $CONFIG->action_token_timeout; - } - $session_id = session_id(); if (($token) && ($ts) && ($session_id)) { // generate token, check with input and forward if invalid - $generated_token = generate_action_token($ts); + $required_token = generate_action_token($ts); // Validate token - if ($token == $generated_token) { - $hour = 60 * 60; - $timeout = $timeout * $hour; - $now = time(); - - // Validate time to ensure its not crazy - if ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout)) { + if ($token == $required_token) { + + if (_elgg_validate_token_timestamp($ts)) { // We have already got this far, so unless anything - // else says something to the contry we assume we're ok + // else says something to the contrary we assume we're ok $returnval = true; $returnval = elgg_trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', array( @@ -252,10 +257,20 @@ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) register_error(elgg_echo('actiongatekeeper:pluginprevents')); } } else if ($visibleerrors) { - register_error(elgg_echo('actiongatekeeper:timeerror')); + // this is necessary because of #5133 + if (elgg_is_xhr()) { + register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); + } else { + register_error(elgg_echo('actiongatekeeper:timeerror')); + } } } else if ($visibleerrors) { - register_error(elgg_echo('actiongatekeeper:tokeninvalid')); + // this is necessary because of #5133 + if (elgg_is_xhr()) { + register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); + } else { + register_error(elgg_echo('actiongatekeeper:tokeninvalid')); + } } } else { if (! empty($_SERVER['CONTENT_LENGTH']) && empty($_POST)) { @@ -284,12 +299,33 @@ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) * This function verifies form input for security features (like a generated token), * and forwards if they are invalid. * + * @param string $action The action being performed + * * @return mixed True if valid or redirects. * @access private */ -function action_gatekeeper() { - if (validate_action_token()) { - return TRUE; +function action_gatekeeper($action) { + if ($action === 'login') { + if (validate_action_token(false)) { + return true; + } + + $token = get_input('__elgg_token'); + $ts = (int)get_input('__elgg_ts'); + if ($token && _elgg_validate_token_timestamp($ts)) { + // The tokens are present and the time looks valid: this is probably a mismatch due to the + // login form being on a different domain. + register_error(elgg_echo('actiongatekeeper:crosssitelogin')); + + + forward('login', 'csrf'); + } + + // let the validator send an appropriate msg + validate_action_token(); + + } elseif (validate_action_token()) { + return true; } forward(REFERER, 'csrf'); @@ -328,16 +364,19 @@ function generate_action_token($timestamp) { } /** - * Initialise the site secret hash. + * Initialise the site secret (32 bytes: "z" to indicate format + 186-bit key in Base64 URL). * * Used during installation and saves as a datalist. * + * Note: Old secrets were hex encoded. + * * @return mixed The site secret hash or false * @access private * @todo Move to better file. */ function init_site_secret() { - $secret = md5(rand() . microtime()); + $secret = 'z' . ElggCrypto::getRandomString(31); + if (datalist_set('__site_secret__', $secret)) { return $secret; } @@ -364,6 +403,26 @@ function get_site_secret() { } /** + * Get the strength of the site secret + * + * @return string "strong", "moderate", or "weak" + * @access private + */ +function _elgg_get_site_secret_strength() { + $secret = get_site_secret(); + if ($secret[0] !== 'z') { + $rand_max = getrandmax(); + if ($rand_max < pow(2, 16)) { + return 'weak'; + } + if ($rand_max < pow(2, 32)) { + return 'moderate'; + } + } + return 'strong'; +} + +/** * Check if an action is registered and its script exists. * * @param string $action Action name diff --git a/engine/lib/admin.php b/engine/lib/admin.php index b65d98c95..f36f29668 100644 --- a/engine/lib/admin.php +++ b/engine/lib/admin.php @@ -134,11 +134,11 @@ function elgg_delete_admin_notice($id) { } /** - * List all admin messages. + * Get admin notices. An admin must be logged in since the notices are private. * * @param int $limit Limit * - * @return array List of admin notices + * @return array Array of admin notices * @since 1.8.0 */ function elgg_get_admin_notices($limit = 10) { @@ -158,11 +158,13 @@ function elgg_get_admin_notices($limit = 10) { * @since 1.8.0 */ function elgg_admin_notice_exists($id) { + $old_ia = elgg_set_ignore_access(true); $notice = elgg_get_entities_from_metadata(array( 'type' => 'object', 'subtype' => 'admin_notice', 'metadata_name_value_pair' => array('name' => 'admin_notice_id', 'value' => $id) )); + elgg_set_ignore_access($old_ia); return ($notice) ? TRUE : FALSE; } @@ -233,6 +235,8 @@ 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/site/regenerate_secret', '', 'admin'); elgg_register_action('admin/menu/save', '', 'admin'); @@ -268,8 +272,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 @@ -287,6 +292,7 @@ function admin_init() { elgg_register_admin_menu_item('configure', 'settings', null, 100); elgg_register_admin_menu_item('configure', 'basic', 'settings', 10); elgg_register_admin_menu_item('configure', 'advanced', 'settings', 20); + elgg_register_admin_menu_item('configure', 'advanced/site_secret', 'settings', 25); elgg_register_admin_menu_item('configure', 'menu_items', 'appearance', 30); elgg_register_admin_menu_item('configure', 'profile_fields', 'appearance', 40); // default widgets is added via an event handler elgg_default_widgets_init() in widgets.php @@ -344,7 +350,7 @@ function elgg_admin_add_plugin_settings_menu() { $active_plugins = elgg_get_plugins('active'); if (!$active_plugins) { // nothing added because no items - return FALSE; + return; } foreach ($active_plugins as $plugin) { @@ -378,6 +384,7 @@ function elgg_admin_add_plugin_settings_menu() { */ function elgg_admin_sort_page_menu($hook, $type, $return, $params) { $configure_items = $return['configure']; + /* @var ElggMenuItem[] $configure_items */ foreach ($configure_items as $menu_item) { if ($menu_item->getName() == 'settings') { $settings = $menu_item; @@ -385,6 +392,7 @@ function elgg_admin_sort_page_menu($hook, $type, $return, $params) { } // keep the basic and advanced settings at the top + /* @var ElggMenuItem $settings */ $children = $settings->getChildren(); $site_settings = array_splice($children, 0, 2); usort($children, array('ElggMenuBuilder', 'compareByText')); @@ -421,7 +429,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( @@ -464,14 +472,18 @@ function admin_page_handler($page) { $vars = array('page' => $page); // special page for plugin settings since we create the form for them - if ($page[0] == 'plugin_settings' && isset($page[1]) && - (elgg_view_exists("settings/{$page[1]}/edit") || elgg_view_exists("plugins/{$page[1]}/settings"))) { + if ($page[0] == 'plugin_settings') { + if (isset($page[1]) && (elgg_view_exists("settings/{$page[1]}/edit") || + elgg_view_exists("plugins/{$page[1]}/settings"))) { - $view = 'admin/plugin_settings'; - $plugin = elgg_get_plugin_from_id($page[1]); - $vars['plugin'] = $plugin; + $view = 'admin/plugin_settings'; + $plugin = elgg_get_plugin_from_id($page[1]); + $vars['plugin'] = $plugin; - $title = elgg_echo("admin:{$page[0]}"); + $title = elgg_echo("admin:{$page[0]}"); + } else { + forward('', '404'); + } } else { $view = 'admin/' . implode('/', $page); $title = elgg_echo("admin:{$page[0]}"); @@ -550,7 +562,7 @@ function admin_plugin_screenshot_page_handler($pages) { * * COPYRIGHT.txt * * LICENSE.txt * - * @param type $page + * @param array $pages * @return bool * @access private */ @@ -613,7 +625,11 @@ function admin_markdown_page_handler($pages) { /** * Adds default admin widgets to the admin dashboard. * - * @return void + * @param string $event + * @param string $type + * @param ElggUser $user + * + * @return null|true * @access private */ function elgg_add_admin_widgets($event, $type, $user) { @@ -635,6 +651,7 @@ function elgg_add_admin_widgets($event, $type, $user) { $guid = elgg_create_widget($user->getGUID(), $handler, 'admin'); if ($guid) { $widget = get_entity($guid); + /* @var ElggWidget $widget */ $widget->move($column, $position); } } diff --git a/engine/lib/annotations.php b/engine/lib/annotations.php index 2036ccd61..5e9b530de 100644 --- a/engine/lib/annotations.php +++ b/engine/lib/annotations.php @@ -17,6 +17,7 @@ */ function row_to_elggannotation($row) { if (!($row instanceof stdClass)) { + // @todo should throw in this case? return $row; } @@ -30,7 +31,7 @@ function row_to_elggannotation($row) { * * @param int $id The id of the annotation object being retrieved. * - * @return false|ElggAnnotation + * @return ElggAnnotation|false */ function elgg_get_annotation_from_id($id) { return elgg_get_metastring_based_object_from_id($id, 'annotations'); @@ -195,10 +196,22 @@ function update_annotation($annotation_id, $name, $value, $value_type, $owner_gu * for the proper use of the "calculation" option. * * - * @return mixed + * @return ElggAnnotation[]|mixed * @since 1.8.0 */ function elgg_get_annotations(array $options = array()) { + + // @todo remove support for count shortcut - see #4393 + if (isset($options['__egefac']) && $options['__egefac']) { + unset($options['__egefac']); + } else { + // support shortcut of 'count' => true for 'annotation_calculation' => 'count' + if (isset($options['count']) && $options['count']) { + $options['annotation_calculation'] = 'count'; + unset($options['count']); + } + } + $options['metastring_type'] = 'annotations'; return elgg_get_metastring_based_objects($options); } @@ -211,7 +224,7 @@ function elgg_get_annotations(array $options = array()) { * annotation_name(s), annotation_value(s), or guid(s) must be set. * * @param array $options An options array. {@See elgg_get_annotations()} - * @return mixed Null if the metadata name is invalid. Bool on success or fail. + * @return bool|null true on success, false on failure, null if no annotations to delete. * @since 1.8.0 */ function elgg_delete_annotations(array $options) { @@ -229,16 +242,20 @@ function elgg_delete_annotations(array $options) { * @warning Unlike elgg_get_annotations() this will not accept an empty options array! * * @param array $options An options array. {@See elgg_get_annotations()} - * @return mixed + * @return bool|null true on success, false on failure, null if no annotations disabled. * @since 1.8.0 */ function elgg_disable_annotations(array $options) { if (!elgg_is_valid_options_for_batch_operation($options, 'annotations')) { return false; } + + // if we can see hidden (disabled) we need to use the offset + // otherwise we risk an infinite loop if there are more than 50 + $inc_offset = access_get_show_hidden_status(); $options['metastring_type'] = 'annotations'; - return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', false); + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset); } /** @@ -246,8 +263,11 @@ function elgg_disable_annotations(array $options) { * * @warning Unlike elgg_get_annotations() this will not accept an empty options array! * + * @warning In order to enable annotations, you must first use + * {@link access_show_hidden_entities()}. + * * @param array $options An options array. {@See elgg_get_annotations()} - * @return mixed + * @return bool|null true on success, false on failure, null if no metadata enabled. * @since 1.8.0 */ function elgg_enable_annotations(array $options) { @@ -316,8 +336,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 +354,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 +361,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 @@ -406,8 +423,8 @@ function elgg_list_entities_from_annotations($options = array()) { function elgg_get_entities_from_annotation_calculation($options) { $db_prefix = elgg_get_config('dbprefix'); $defaults = array( - 'calculation' => 'sum', - 'order_by' => 'annotation_calculation desc' + 'calculation' => 'sum', + 'order_by' => 'annotation_calculation desc' ); $options = array_merge($defaults, $options); @@ -427,6 +444,10 @@ function elgg_get_entities_from_annotation_calculation($options) { $options['callback'] = 'entity_row_to_elggstar'; + // see #4393 + // @todo remove after the 'count' shortcut is removed from elgg_get_annotations() + $options['__egefac'] = true; + return elgg_get_annotations($options); } @@ -440,23 +461,30 @@ function elgg_get_entities_from_annotation_calculation($options) { * @return string */ function elgg_list_entities_from_annotation_calculation($options) { + $defaults = array( + 'calculation' => 'sum', + 'order_by' => 'annotation_calculation desc' + ); + $options = array_merge($defaults, $options); + return elgg_list_entities($options, 'elgg_get_entities_from_annotation_calculation'); } /** - * Handler called by trigger_plugin_hook on the "export" event. + * Export the annotations for the specified entity * * @param string $hook 'export' - * @param string $entity_type 'all' + * @param string $type 'all' * @param mixed $returnvalue Default return value - * @param mixed $params List of params to export + * @param mixed $params Parameters determining what annotations to export * * @elgg_plugin_hook export all * - * @return mixed + * @return array + * @throws InvalidParameterException * @access private */ -function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $params) { +function export_annotation_plugin_hook($hook, $type, $returnvalue, $params) { // Sanity check values if ((!is_array($params)) && (!isset($params['guid']))) { throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); @@ -467,12 +495,12 @@ function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $param } $guid = (int)$params['guid']; - $name = $params['name']; + $options = array('guid' => $guid, 'limit' => 0); + if (isset($params['name'])) { + $options['annotation_name'] = $params['name']; + } - $result = elgg_get_annotations(array( - 'guid' => $guid, - 'limit' => 0 - )); + $result = elgg_get_annotations($options); if ($result) { foreach ($result as $r) { @@ -517,15 +545,16 @@ function elgg_annotation_exists($entity_guid, $annotation_type, $owner_guid = NU return FALSE; } - $entity_guid = (int)$entity_guid; - $annotation_type = sanitise_string($annotation_type); + $entity_guid = sanitize_int($entity_guid); + $owner_guid = sanitize_int($owner_guid); + $annotation_type = sanitize_string($annotation_type); - $sql = "select a.id" . - " FROM {$CONFIG->dbprefix}annotations a, {$CONFIG->dbprefix}metastrings m " . - " WHERE a.owner_guid={$owner_guid} AND a.entity_guid={$entity_guid} " . - " AND a.name_id=m.id AND m.string='{$annotation_type}'"; + $sql = "SELECT a.id FROM {$CONFIG->dbprefix}annotations a" . + " JOIN {$CONFIG->dbprefix}metastrings m ON a.name_id = m.id" . + " WHERE a.owner_guid = $owner_guid AND a.entity_guid = $entity_guid" . + " AND m.string = '$annotation_type'"; - if ($check_annotation = get_data_row($sql)) { + if (get_data_row($sql)) { return TRUE; } @@ -544,6 +573,7 @@ function elgg_comment_url_handler(ElggAnnotation $comment) { if ($entity) { return $entity->getURL() . '#item-annotation-' . $comment->id; } + return ""; } /** @@ -560,6 +590,12 @@ function elgg_register_annotation_url_handler($extender_name = "all", $function_ /** * Register annotation unit tests + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array * @access private */ function annotations_test($hook, $type, $value, $params) { diff --git a/engine/lib/cache.php b/engine/lib/cache.php index be1c43e14..3116c1a9b 100644 --- a/engine/lib/cache.php +++ b/engine/lib/cache.php @@ -125,7 +125,7 @@ function elgg_get_filepath_cache() { * @access private */ function elgg_filepath_cache_reset() { - return elgg_reset_system_cache(); + elgg_reset_system_cache(); } /** * @access private @@ -143,13 +143,13 @@ function elgg_filepath_cache_load($type) { * @access private */ function elgg_enable_filepath_cache() { - return elgg_enable_system_cache(); + elgg_enable_system_cache(); } /** * @access private */ function elgg_disable_filepath_cache() { - return elgg_disable_system_cache(); + elgg_disable_system_cache(); } /* Simplecache */ @@ -208,6 +208,7 @@ function elgg_get_simplecache_url($type, $view) { global $CONFIG; $lastcache = (int)$CONFIG->lastcache; $viewtype = elgg_get_viewtype(); + elgg_register_simplecache_view("$type/$view");// see #5302 if (elgg_is_simplecache_enabled()) { $url = elgg_get_site_url() . "cache/$type/$viewtype/$view.$lastcache.$type"; } else { @@ -222,7 +223,7 @@ function elgg_get_simplecache_url($type, $view) { /** * Regenerates the simple cache. * - * @warning This does not invalidate the cache, but actively resets it. + * @warning This does not invalidate the cache, but actively rebuilds it. * * @param string $viewtype Optional viewtype to regenerate. Defaults to all valid viewtypes. * @@ -444,7 +445,7 @@ function _elgg_cache_init() { if ($CONFIG->system_cache_enabled && !$CONFIG->i18n_loaded_from_cache) { reload_all_translations(); foreach ($CONFIG->translations as $lang => $map) { - elgg_save_system_cache("$lang.php", serialize($map)); + elgg_save_system_cache("$lang.lang", serialize($map)); } } } diff --git a/engine/lib/calendar.php b/engine/lib/calendar.php index 9a06c5292..e6f95934c 100644 --- a/engine/lib/calendar.php +++ b/engine/lib/calendar.php @@ -39,6 +39,8 @@ function get_day_end($day = null, $month = null, $year = null) { /** * Return the notable entities for a given time period. * + * @todo this function also accepts an array(type => subtypes) for 3rd arg. Should we document this? + * * @param int $start_time The start time as a unix timestamp. * @param int $end_time The end time as a unix timestamp. * @param string $type The type of entity (eg "user", "object" etc) diff --git a/engine/lib/configuration.php b/engine/lib/configuration.php index 305aa00b6..55e5bbd36 100644 --- a/engine/lib/configuration.php +++ b/engine/lib/configuration.php @@ -36,6 +36,7 @@ function elgg_get_site_url($site_guid = 0) { if (!$site instanceof ElggSite) { return false; } + /* @var ElggSite $site */ return $site->url; } @@ -91,23 +92,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 +139,7 @@ function elgg_set_config($name, $value) { /** * Save a configuration setting * - * @param string $name Configuration name (cannot be greater than 32 characters) + * @param string $name Configuration name (cannot be greater than 255 characters) * @param mixed $value Configuration value. Should be string for installation setting * @param int $site_guid NULL for installation setting, 0 for default site * @@ -167,7 +174,7 @@ function elgg_save_config($name, $value, $site_guid = 0) { /** * Check that installation has completed and the database is populated. * - * @throws InstallationException + * @throws InstallationException|DatabaseException * @return void * @access private */ @@ -175,7 +182,7 @@ function verify_installation() { global $CONFIG; if (isset($CONFIG->installed)) { - return $CONFIG->installed; + return; } try { @@ -221,9 +228,9 @@ function datalist_get($name) { $name = trim($name); - // cannot store anything longer than 32 characters in db, so catch here - if (elgg_strlen($name) > 32) { - elgg_log("The name length for configuration variables cannot be greater than 32", "ERROR"); + // cannot store anything longer than 255 characters in db, so catch here + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); return false; } @@ -280,7 +287,7 @@ function datalist_get($name) { function datalist_set($name, $value) { global $CONFIG, $DATALIST_CACHE; - // cannot store anything longer than 32 characters in db, so catch before we set + // cannot store anything longer than 255 characters in db, so catch before we set if (elgg_strlen($name) > 255) { elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); return false; @@ -326,7 +333,7 @@ function datalist_set($name, $value) { * This will cause the run once function to be run on all installations. To perform * additional upgrades, create new functions for each release. * - * @warning The function name cannot be longer than 32 characters long due to + * @warning The function name cannot be longer than 255 characters long due to * the current schema for the datalist table. * * @internal A datalist entry $functioname is created with the value of time(). @@ -401,7 +408,7 @@ function unset_config($name, $site_guid = 0) { * @param string $value Its value * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default) * - * @return 0 + * @return bool * @todo The config table doens't have numeric primary keys so insert_data returns 0. * @todo Use "INSERT ... ON DUPLICATE KEY UPDATE" instead of trying to delete then add. * @see unset_config() @@ -413,9 +420,9 @@ function set_config($name, $value, $site_guid = 0) { $name = trim($name); - // cannot store anything longer than 32 characters in db, so catch before we set - if (elgg_strlen($name) > 32) { - elgg_log("The name length for configuration variables cannot be greater than 32", "ERROR"); + // cannot store anything longer than 255 characters in db, so catch before we set + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); return false; } @@ -479,9 +486,9 @@ function get_config($name, $site_guid = 0) { // @todo these haven't really been implemented in Elgg 1.8. Complete in 1.9. // show dep message if ($new_name) { - // $msg = "Config value $name has been renamed as $new_name"; + // $msg = "Config value $name has been renamed as $new_name"; $name = $new_name; - // elgg_deprecated_notice($msg, $dep_version); + // elgg_deprecated_notice($msg, $dep_version); } // decide from where to return the value @@ -558,6 +565,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/cron.php b/engine/lib/cron.php index f7a032f4a..4f3d05b93 100644 --- a/engine/lib/cron.php +++ b/engine/lib/cron.php @@ -26,11 +26,10 @@ function cron_init() { * @param array $page Pages * * @return bool + * @throws CronException * @access private */ function cron_page_handler($page) { - global $CONFIG; - if (!isset($page[0])) { forward(); } @@ -51,7 +50,6 @@ function cron_page_handler($page) { $params['time'] = time(); // Data to return to - $std_out = ""; $old_stdout = ""; ob_start(); diff --git a/engine/lib/database.php b/engine/lib/database.php index 7d90b30b8..a7949788d 100644 --- a/engine/lib/database.php +++ b/engine/lib/database.php @@ -12,15 +12,19 @@ /** * Query cache for all queries. * - * Each query and its results are stored in this array as: + * Each query and its results are stored in this cache as: * <code> - * $DB_QUERY_CACHE[$query] => array(result1, result2, ... resultN) + * $DB_QUERY_CACHE[query hash] => array(result1, result2, ... resultN) * </code> + * @see elgg_query_runner() for details on the hash. * - * @global array $DB_QUERY_CACHE + * @warning Elgg used to set this as an empty array to turn off the cache + * + * @global ElggLRUCache|null $DB_QUERY_CACHE + * @access private */ global $DB_QUERY_CACHE; -$DB_QUERY_CACHE = array(); +$DB_QUERY_CACHE = null; /** * Queries to be executed upon shutdown. @@ -38,6 +42,7 @@ $DB_QUERY_CACHE = array(); * </code> * * @global array $DB_DELAYED_QUERIES + * @access private */ global $DB_DELAYED_QUERIES; $DB_DELAYED_QUERIES = array(); @@ -48,7 +53,8 @@ $DB_DELAYED_QUERIES = array(); * Each database link created with establish_db_link($name) is stored in * $dblink as $dblink[$name] => resource. Use get_db_link($name) to retrieve it. * - * @global array $dblink + * @global resource[] $dblink + * @access private */ global $dblink; $dblink = array(); @@ -59,6 +65,7 @@ $dblink = array(); * Each call to the database increments this counter. * * @global integer $dbcalls + * @access private */ global $dbcalls; $dbcalls = 0; @@ -72,11 +79,12 @@ $dbcalls = 0; * resource. eg "read", "write", or "readwrite". * * @return void + * @throws DatabaseException * @access private */ function establish_db_link($dblinkname = "readwrite") { // Get configuration, and globalise database link - global $CONFIG, $dblink, $DB_QUERY_CACHE, $dbcalls; + global $CONFIG, $dblink, $DB_QUERY_CACHE; if ($dblinkname != "readwrite" && isset($CONFIG->db[$dblinkname])) { if (is_array($CONFIG->db[$dblinkname])) { @@ -120,7 +128,8 @@ function establish_db_link($dblinkname = "readwrite") { // Set up cache if global not initialized and query cache not turned off if ((!$DB_QUERY_CACHE) && (!$db_cache_off)) { - $DB_QUERY_CACHE = new ElggStaticVariableCache('db_query_cache'); + // @todo if we keep this cache in 1.9, expose the size as a config parameter + $DB_QUERY_CACHE = new ElggLRUCache(200); } } @@ -134,7 +143,7 @@ function establish_db_link($dblinkname = "readwrite") { * @access private */ function setup_db_connections() { - global $CONFIG, $dblink; + global $CONFIG; if (!empty($CONFIG->db->split)) { establish_db_link('read'); @@ -197,7 +206,7 @@ function db_delayedexecution_shutdown_hook() { * * @param string $dblinktype The type of link we want: "read", "write" or "readwrite". * - * @return object Database link + * @return resource Database link * @access private */ function get_db_link($dblinktype) { @@ -216,7 +225,7 @@ function get_db_link($dblinktype) { /** * Execute an EXPLAIN for $query. * - * @param str $query The query to explain + * @param string $query The query to explain * @param mixed $link The database link resource to user. * * @return mixed An object of the query's result, or FALSE @@ -240,14 +249,14 @@ function explain_query($query, $link) { * {@link $dbcalls} is incremented and the query is saved into the {@link $DB_QUERY_CACHE}. * * @param string $query The query - * @param link $dblink The DB link + * @param resource $dblink The DB link * - * @return The result of mysql_query() + * @return resource result of mysql_query() * @throws DatabaseException * @access private */ function execute_query($query, $dblink) { - global $CONFIG, $dbcalls; + global $dbcalls; if ($query == NULL) { throw new DatabaseException(elgg_echo('DatabaseException:InvalidQuery')); @@ -275,7 +284,7 @@ function execute_query($query, $dblink) { * the raw result from {@link mysql_query()}. * * @param string $query The query to execute - * @param resource $dblink The database link to use or the link type (read | write) + * @param resource|string $dblink The database link to use or the link type (read | write) * @param string $handler A callback function to pass the results array to * * @return true @@ -386,20 +395,18 @@ function get_data_row($query, $callback = "") { * @access private */ function elgg_query_runner($query, $callback = null, $single = false) { - global $CONFIG, $DB_QUERY_CACHE; + global $DB_QUERY_CACHE; // Since we want to cache results of running the callback, we need to // need to namespace the query with the callback and single result request. - // http://trac.elgg.org/ticket/4049 + // https://github.com/elgg/elgg/issues/4049 $hash = (string)$callback . (int)$single . $query; // Is cached? if ($DB_QUERY_CACHE) { - $cached_query = $DB_QUERY_CACHE[$hash]; - - if ($cached_query !== FALSE) { + if (isset($DB_QUERY_CACHE[$hash])) { elgg_log("DB query $query results returned from cache (hash: $hash)", 'NOTICE'); - return $cached_query; + return $DB_QUERY_CACHE[$hash]; } } @@ -410,7 +417,7 @@ function elgg_query_runner($query, $callback = null, $single = false) { // test for callback once instead of on each iteration. // @todo check profiling to see if this needs to be broken out into - // explicit cases instead of checking in the interation. + // explicit cases instead of checking in the iteration. $is_callable = is_callable($callback); while ($row = mysql_fetch_object($result)) { if ($is_callable) { @@ -451,18 +458,12 @@ function elgg_query_runner($query, $callback = null, $single = false) { * @access private */ function insert_data($query) { - global $CONFIG, $DB_QUERY_CACHE; elgg_log("DB query $query", 'NOTICE'); $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - } - - elgg_log("Query cache invalidated", 'NOTICE'); + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return mysql_insert_id($dblink); @@ -472,7 +473,7 @@ function insert_data($query) { } /** - * Update a row in the database. + * Update the database. * * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. * @@ -482,17 +483,12 @@ function insert_data($query) { * @access private */ function update_data($query) { - global $CONFIG, $DB_QUERY_CACHE; elgg_log("DB query $query", 'NOTICE'); $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - elgg_log("Query cache invalidated", 'NOTICE'); - } + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return TRUE; @@ -502,7 +498,7 @@ function update_data($query) { } /** - * Remove a row from the database. + * Remove data from the database. * * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. * @@ -512,17 +508,12 @@ function update_data($query) { * @access private */ function delete_data($query) { - global $CONFIG, $DB_QUERY_CACHE; elgg_log("DB query $query", 'NOTICE'); $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - elgg_log("Query cache invalidated", 'NOTICE'); - } + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return mysql_affected_rows($dblink); @@ -531,6 +522,22 @@ function delete_data($query) { return FALSE; } +/** + * Invalidate the query cache + * + * @access private + */ +function _elgg_invalidate_query_cache() { + global $DB_QUERY_CACHE; + if ($DB_QUERY_CACHE instanceof ElggLRUCache) { + $DB_QUERY_CACHE->clear(); + elgg_log("Query cache invalidated", 'NOTICE'); + } elseif ($DB_QUERY_CACHE) { + // In case someone sets the cache to an array and primes it with data + $DB_QUERY_CACHE = array(); + elgg_log("Query cache invalidated", 'NOTICE'); + } +} /** * Return tables matching the database prefix {@link $CONFIG->dbprefix}% in the currently @@ -638,7 +645,7 @@ function run_sql_script($scriptlocation) { $statement = str_replace("prefix_", $CONFIG->dbprefix, $statement); if (!empty($statement)) { try { - $result = update_data($statement); + update_data($statement); } catch (DatabaseException $e) { $errors[] = $e->getMessage(); } @@ -661,7 +668,7 @@ function run_sql_script($scriptlocation) { /** * Format a query string for logging - * + * * @param string $query Query string * @return string * @access private diff --git a/engine/lib/deprecated-1.7.php b/engine/lib/deprecated-1.7.php index 519eea89d..ee95b5611 100644 --- a/engine/lib/deprecated-1.7.php +++ b/engine/lib/deprecated-1.7.php @@ -1137,6 +1137,7 @@ function make_register_object($register_name, $register_value, $children_array = * @param int $guid GUID * * @return 1 + * @deprecated 1.7 */ function delete_object_entity($guid) { system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); @@ -1154,6 +1155,7 @@ function delete_object_entity($guid) { * @param int $guid User GUID * * @return 1 + * @deprecated 1.7 */ function delete_user_entity($guid) { system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); diff --git a/engine/lib/deprecated-1.8.php b/engine/lib/deprecated-1.8.php index 4b9d41543..91068d047 100644 --- a/engine/lib/deprecated-1.8.php +++ b/engine/lib/deprecated-1.8.php @@ -87,7 +87,7 @@ function list_entities_from_access_id($access_id, $entity_type = "", $entity_sub elgg_deprecated_notice("All list_entities* functions were deprecated in 1.8. Use elgg_list_entities* instead.", 1.8); echo elgg_list_entities_from_access_id(array('access_id' => $access_id, - 'types' => $entity_type, 'subtypes' => $entity_subtype, 'owner_guids' => $owner_guid, + 'type' => $entity_type, 'subtype' => $entity_subtype, 'owner_guids' => $owner_guid, 'limit' => $limit, 'full_view' => $fullview, 'list_type_toggle' => $listtypetoggle, 'pagination' => $pagination,)); } @@ -1314,8 +1314,8 @@ function list_entities_from_metadata($meta_name, $meta_value = "", $entity_type $options = array( 'metadata_name' => $meta_name, 'metadata_value' => $meta_value, - 'types' => $entity_type, - 'subtypes' => $entity_subtype, + 'type' => $entity_type, + 'subtype' => $entity_subtype, 'limit' => $limit, 'offset' => $offset, 'count' => TRUE, @@ -2120,8 +2120,8 @@ $fullview = true, $listtypetoggle = false, $pagination = true, $order_by = '') { 'relationship' => $relationship, 'relationship_guid' => $relationship_guid, 'inverse_relationship' => $inverse_relationship, - 'types' => $type, - 'subtypes' => $subtype, + 'type' => $type, + 'subtype' => $subtype, 'owner_guid' => $owner_guid, 'order_by' => $order_by, 'limit' => $limit, @@ -2566,9 +2566,9 @@ $owner_guid = "", $owner_relationship = "") { 'relationship' => $owner_relationship, 'relationship_guid' => $owner_guid[0], 'inverse_relationship' => FALSE, - 'types' => 'user', - 'subtypes' => $subtype, - 'limit' => 9999)) + 'type' => 'user', + 'subtype' => $subtype, + 'limit' => false)) ) { $friendsarray = array(); @@ -2721,8 +2721,8 @@ function get_site_collections($site_guid, $subtype = "", $limit = 10, $offset = 'relationship' => 'member_of_site', 'relationship_guid' => $site_guid, 'inverse_relationship' => TRUE, - 'types' => 'collection', - 'subtypes' => $subtype, + 'type' => 'collection', + 'subtype' => $subtype, 'limit' => $limit, 'offset' => $offset )); @@ -3414,6 +3414,7 @@ function list_annotations($entity_guid, $name = "", $limit = 25, $asc = true) { * @param unknown_type $timeupper * @param unknown_type $calculation * @internal Don't use this at all. + * @deprecated 1.8 Use elgg_get_annotations() */ function elgg_deprecated_annotation_calculation($entity_guid = 0, $entity_type = "", $entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, @@ -4667,6 +4668,7 @@ function display_widget(ElggObject $widget) { * * @param ElggEntity $entity * @return int Number of comments + * @deprecated 1.8 Use ElggEntity->countComments() */ function elgg_count_comments($entity) { elgg_deprecated_notice('elgg_count_comments() is deprecated by ElggEntity->countComments()', 1.8); @@ -4772,3 +4774,47 @@ function default_page_handler($page, $handler) { return FALSE; } + +/** + * Invalidate this class's entry in the cache. + * + * @param int $guid The entity guid + * + * @return void + * @access private + * @deprecated 1.8 + */ +function invalidate_cache_for_entity($guid) { + elgg_deprecated_notice('invalidate_cache_for_entity() is a private function and should not be used.', 1.8); + _elgg_invalidate_cache_for_entity($guid); +} + +/** + * Cache an entity. + * + * Stores an entity in $ENTITY_CACHE; + * + * @param ElggEntity $entity Entity to cache + * + * @return void + * @access private + * @deprecated 1.8 + */ +function cache_entity(ElggEntity $entity) { + elgg_deprecated_notice('cache_entity() is a private function and should not be used.', 1.8); + _elgg_cache_entity($entity); +} + +/** + * Retrieve a entity from the cache. + * + * @param int $guid The guid + * + * @return ElggEntity|bool false if entity not cached, or not fully loaded + * @access private + * @deprecated 1.8 + */ +function retrieve_cached_entity($guid) { + elgg_deprecated_notice('retrieve_cached_entity() is a private function and should not be used.', 1.8); + return _elgg_retrieve_cached_entity($guid); +} diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index 26c1cccfd..34111c69d 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -93,10 +93,17 @@ function elgg_register_library($name, $location) { * @return void * @throws InvalidParameterException * @since 1.8.0 + * @todo return boolean in 1.9 to indicate whether the library has been loaded */ function elgg_load_library($name) { global $CONFIG; + static $loaded_libraries = array(); + + if (in_array($name, $loaded_libraries)) { + return; + } + if (!isset($CONFIG->libraries)) { $CONFIG->libraries = array(); } @@ -113,6 +120,8 @@ function elgg_load_library($name) { ); throw new InvalidParameterException($error); } + + $loaded_libraries[] = $name; } /** @@ -124,12 +133,11 @@ function elgg_load_library($name) { * @param string $location URL to forward to browser to. Can be path relative to the network's URL. * @param string $reason Short explanation for why we're forwarding * - * @return False False if headers have been sent. Terminates execution if forwarding. + * @return false False if headers have been sent. Terminates execution if forwarding. + * @throws SecurityException */ function forward($location = "", $reason = 'system') { - global $CONFIG; - - if (!headers_sent()) { + if (!headers_sent($file, $line)) { if ($location === REFERER) { $location = $_SERVER['HTTP_REFERER']; } @@ -148,7 +156,7 @@ function forward($location = "", $reason = 'system') { exit; } } else { - throw new SecurityException(elgg_echo('SecurityException:ForwardFailedToRedirect')); + throw new SecurityException(elgg_echo('SecurityException:ForwardFailedToRedirect', array($file, $line))); } } @@ -384,7 +392,7 @@ function elgg_load_external_file($type, $name) { $item->url = ''; $item->location = ''; - $priority = $CONFIG->externals[$type]->add($item); + $CONFIG->externals[$type]->add($item); $CONFIG->externals_map[$type][$name] = $item; } } @@ -528,7 +536,7 @@ function sanitise_filepath($path, $append_slash = TRUE) { * @param string $register Types of message: "error", "success" (default: success) * @param bool $count Count the number of messages (default: false) * - * @return true|false|array Either the array of messages, or a response regarding + * @return bool|array Either the array of messages, or a response regarding * whether the message addition was successful. * @todo Clean up. Separate registering messages and retrieving them. */ @@ -562,7 +570,7 @@ function system_messages($message = null, $register = "success", $count = false) return sizeof($_SESSION['msg'][$register]); } else { $count = 0; - foreach ($_SESSION['msg'] as $register => $submessages) { + foreach ($_SESSION['msg'] as $submessages) { $count += sizeof($submessages); } return $count; @@ -671,7 +679,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 +692,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 +703,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 +718,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]); + } } } } @@ -735,7 +746,7 @@ function elgg_unregister_event_handler($event, $object_type, $callback) { * @tip When referring to events, the preferred syntax is "event, type". * * @internal Only rarely should events be changed, added, or removed in core. - * When making changes to events, be sure to first create a ticket in trac. + * When making changes to events, be sure to first create a ticket on Github. * * @internal @tip Think of $object_type as the primary namespace element, and * $event as the secondary namespace. @@ -770,14 +781,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; } /** @@ -836,7 +847,7 @@ function elgg_trigger_event($event, $object_type, $object = null) { * * @param string $hook The name of the hook * @param string $type The type of the hook - * @param callback $callback The name of a valid function or an array with object and method + * @param callable $callback The name of a valid function or an array with object and method * @param int $priority The priority - 500 is default, lower numbers called first * * @return bool @@ -850,7 +861,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 +874,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 +885,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; } /** @@ -882,16 +893,19 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority = * * @param string $hook The name of the hook * @param string $entity_type The name of the type of entity (eg "user", "object" etc) - * @param callback $callback The PHP callback to be removed + * @param callable $callback The PHP callback to be removed * * @return void * @since 1.8.0 */ 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 +984,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; + } } } } @@ -1052,6 +1068,7 @@ function _elgg_php_exception_handler($exception) { * @param array $vars An array that points to the active symbol table where error occurred * * @return true + * @throws Exception * @access private * @todo Replace error_log calls with elgg_log calls. */ @@ -1071,8 +1088,8 @@ function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { case E_USER_WARNING : case E_RECOVERABLE_ERROR: // (e.g. type hint violation) - // check if the error wasn't suppressed by @-functionname - if(error_reporting()){ + // check if the error wasn't suppressed by the error control operator (@) + if (error_reporting()) { error_log("PHP WARNING: $error"); } break; @@ -1177,6 +1194,11 @@ function elgg_dump($value, $to_screen = TRUE, $level = 'NOTICE') { $to_screen = FALSE; } + // Do not want to write to JS or CSS pages + if (elgg_in_context('js') || elgg_in_context('css')) { + $to_screen = FALSE; + } + if ($to_screen == TRUE) { echo '<pre>'; print_r($value); @@ -1284,8 +1306,6 @@ function elgg_deprecated_notice($msg, $dep_version, $backtrace_level = 1) { * @return string The current page URL. */ function current_page_url() { - global $CONFIG; - $url = parse_url(elgg_get_site_url()); $page = $url['scheme'] . "://"; @@ -1330,7 +1350,7 @@ function full_url() { "" : (":" . $_SERVER["SERVER_PORT"]); // This is here to prevent XSS in poorly written browsers used by 80% of the population. - // {@trac [5813]} + // https://github.com/Elgg/Elgg/commit/0c947e80f512cb0a482b1864fd0a6965c8a0cd4a $quotes = array('\'', '"'); $encoded = array('%27', '%22'); @@ -1346,7 +1366,7 @@ function full_url() { * @param array $parts Associative array of URL components like parse_url() returns * @param bool $html_encode HTML Encode the url? * - * @return str Full URL + * @return string Full URL * @since 1.7.0 */ function elgg_http_build_url(array $parts, $html_encode = TRUE) { @@ -1377,10 +1397,10 @@ function elgg_http_build_url(array $parts, $html_encode = TRUE) { * add tokens to the action. The form view automatically handles * tokens. * - * @param str $url Full action URL - * @param bool $html_encode HTML encode the url? (default: false) + * @param string $url Full action URL + * @param bool $html_encode HTML encode the url? (default: false) * - * @return str URL with action tokens + * @return string URL with action tokens * @since 1.7.0 * @link http://docs.elgg.org/Tutorials/Actions */ @@ -1432,17 +1452,17 @@ function elgg_http_remove_url_query_element($url, $element) { } $url_array['query'] = http_build_query($query); - $string = elgg_http_build_url($url_array); + $string = elgg_http_build_url($url_array, false); return $string; } /** * Adds an element or elements to a URL's query string. * - * @param str $url The URL - * @param array $elements Key/value pairs to add to the URL + * @param string $url The URL + * @param array $elements Key/value pairs to add to the URL * - * @return str The new URL with the query strings added + * @return string The new URL with the query strings added * @since 1.7.0 */ function elgg_http_add_url_query_elements($url, array $elements) { @@ -1479,8 +1499,6 @@ function elgg_http_add_url_query_elements($url, array $elements) { * @since 1.8.0 */ function elgg_http_url_is_identical($url1, $url2, $ignore_params = array('offset', 'limit')) { - global $CONFIG; - // if the server portion is missing but it starts with / then add the url in. // @todo use elgg_normalize_url() if (elgg_substr($url1, 0, 1) == '/') { @@ -1619,7 +1637,7 @@ $sort_type = SORT_LOCALE_STRING) { $sort = array(); - foreach ($array as $k => $v) { + foreach ($array as $v) { if (isset($v[$element])) { $sort[] = strtolower($v[$element]); } else { @@ -1638,7 +1656,7 @@ $sort_type = SORT_LOCALE_STRING) { * * @param string $ini_get_arg The INI setting * - * @return true|false Depending on whether it's on or off + * @return bool Depending on whether it's on or off */ function ini_get_bool($ini_get_arg) { $temp = strtolower(ini_get($ini_get_arg)); @@ -1654,7 +1672,7 @@ function ini_get_bool($ini_get_arg) { * * @tip Use this for arithmetic when determining if a file can be uploaded. * - * @param str $setting The php.ini setting + * @param string $setting The php.ini setting * * @return int * @since 1.7.0 @@ -1669,8 +1687,10 @@ function elgg_get_ini_setting_in_bytes($setting) { switch($last) { case 'g': $val *= 1024; + // fallthrough intentional case 'm': $val *= 1024; + // fallthrough intentional case 'k': $val *= 1024; } @@ -1827,7 +1847,7 @@ function elgg_ajax_page_handler($page) { * * @param array $page The page array * - * @return void + * @return bool * @elgg_pagehandler css * @access private */ @@ -1883,7 +1903,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)); @@ -1891,6 +1911,7 @@ function elgg_cacheable_view_page_handler($page, $type) { echo $return; return true; } + return false; } /** @@ -2212,7 +2233,7 @@ function elgg_init() { * @param array $params empty * * @elgg_plugin_hook unit_tests system - * @return void + * @return array * @access private */ function elgg_api_test($hook, $type, $value, $params) { @@ -2224,7 +2245,10 @@ function elgg_api_test($hook, $type, $value, $params) { } /**#@+ - * Controlls access levels on ElggEntity entities, metadata, and annotations. + * Controls access levels on ElggEntity entities, metadata, and annotations. + * + * @warning ACCESS_DEFAULT is a place holder for the input/access view. Do not + * use it when saving an entity. * * @var int */ @@ -2258,7 +2282,7 @@ define('ELGG_ENTITIES_NO_VALUE', 0); * referring page. * * @see forward - * @var unknown_type + * @var int -1 */ define('REFERRER', -1); diff --git a/engine/lib/entities.php b/engine/lib/entities.php index a50567d9f..4fcf1c657 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -17,23 +17,60 @@ global $ENTITY_CACHE; $ENTITY_CACHE = array(); /** - * Cache subtypes and related class names once loaded. + * GUIDs of entities banned from the entity cache (during this request) * - * @global array $SUBTYPE_CACHE + * @global array $ENTITY_CACHE_DISABLED_GUIDS + * @access private + */ +global $ENTITY_CACHE_DISABLED_GUIDS; +$ENTITY_CACHE_DISABLED_GUIDS = array(); + +/** + * Cache subtypes and related class names. + * + * @global array|null $SUBTYPE_CACHE array once populated from DB, initially null * @access private */ global $SUBTYPE_CACHE; -$SUBTYPE_CACHE = NULL; +$SUBTYPE_CACHE = null; + +/** + * Remove this entity from the entity cache and make sure it is not re-added + * + * @param int $guid The entity guid + * + * @access private + * @todo this is a workaround until #5604 can be implemented + */ +function _elgg_disable_caching_for_entity($guid) { + global $ENTITY_CACHE_DISABLED_GUIDS; + + _elgg_invalidate_cache_for_entity($guid); + $ENTITY_CACHE_DISABLED_GUIDS[$guid] = true; +} + +/** + * Allow this entity to be stored in the entity cache + * + * @param int $guid The entity guid + * + * @access private + */ +function _elgg_enable_caching_for_entity($guid) { + global $ENTITY_CACHE_DISABLED_GUIDS; + + unset($ENTITY_CACHE_DISABLED_GUIDS[$guid]); +} /** * Invalidate this class's entry in the cache. * * @param int $guid The entity guid * - * @return null + * @return void * @access private */ -function invalidate_cache_for_entity($guid) { +function _elgg_invalidate_cache_for_entity($guid) { global $ENTITY_CACHE; $guid = (int)$guid; @@ -50,28 +87,41 @@ function invalidate_cache_for_entity($guid) { * * @param ElggEntity $entity Entity to cache * - * @return null - * @see retrieve_cached_entity() - * @see invalidate_cache_for_entity() + * @return void + * @see _elgg_retrieve_cached_entity() + * @see _elgg_invalidate_cache_for_entity() * @access private - * TODO(evan): Use an ElggCache object + * @todo Use an ElggCache object */ -function cache_entity(ElggEntity $entity) { - global $ENTITY_CACHE; +function _elgg_cache_entity(ElggEntity $entity) { + global $ENTITY_CACHE, $ENTITY_CACHE_DISABLED_GUIDS; - // Don't cache 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; + } + + $guid = $entity->getGUID(); + if (isset($ENTITY_CACHE_DISABLED_GUIDS[$guid])) { return; } // Don't store too many or we'll have memory problems - // TODO(evan): Pick a less arbitrary limit + // @todo Pick a less arbitrary limit if (count($ENTITY_CACHE) > 256) { - 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; + $ENTITY_CACHE[$guid] = $entity; } /** @@ -80,15 +130,13 @@ function cache_entity(ElggEntity $entity) { * @param int $guid The guid * * @return ElggEntity|bool false if entity not cached, or not fully loaded - * @see cache_entity() - * @see invalidate_cache_for_entity() + * @see _elgg_cache_entity() + * @see _elgg_invalidate_cache_for_entity() * @access private */ -function retrieve_cached_entity($guid) { +function _elgg_retrieve_cached_entity($guid) { global $ENTITY_CACHE; - $guid = (int)$guid; - if (isset($ENTITY_CACHE[$guid])) { if ($ENTITY_CACHE[$guid]->isFullyLoaded()) { return $ENTITY_CACHE[$guid]; @@ -99,31 +147,6 @@ function retrieve_cached_entity($guid) { } /** - * As retrieve_cached_entity, but returns the result as a stdClass - * (compatible with load functions that expect a database row.) - * - * @param int $guid The guid - * - * @return mixed - * @todo unused - * @access private - */ -function retrieve_cached_entity_row($guid) { - $obj = retrieve_cached_entity($guid); - if ($obj) { - $tmp = new stdClass; - - foreach ($obj as $k => $v) { - $tmp->$k = $v; - } - - return $tmp; - } - - return false; -} - -/** * Return the id for a given subtype. * * ElggEntity objects have a type and a subtype. Subtypes @@ -148,29 +171,23 @@ function retrieve_cached_entity_row($guid) { * @access private */ function get_subtype_id($type, $subtype) { - global $CONFIG, $SUBTYPE_CACHE; + global $SUBTYPE_CACHE; - $type = sanitise_string($type); - $subtype = sanitise_string($subtype); - - 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; } /** @@ -178,35 +195,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; +} - $SUBTYPE_CACHE[$subtype_id] = $result; - return $result->subtype; +/** + * 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; + + 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; + } } /** @@ -225,25 +274,19 @@ function get_subtype_from_id($subtype_id) { * @access private */ function get_subtype_class($type, $subtype) { - global $CONFIG, $SUBTYPE_CACHE; + global $SUBTYPE_CACHE; - $type = sanitise_string($type); - $subtype = sanitise_string($subtype); - - // @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; } /** @@ -257,29 +300,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; } /** @@ -305,21 +340,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; @@ -361,22 +407,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; } /** @@ -394,7 +449,7 @@ function update_subtype($type, $subtype, $class = '') { * @param int $time_created The time creation timestamp * * @return bool - * @link http://docs.elgg.org/DataModel/Entities + * @throws InvalidParameterException * @access private */ function update_entity($guid, $owner_guid, $access_id, $container_guid = null, $time_created = null) { @@ -417,6 +472,10 @@ function update_entity($guid, $owner_guid, $access_id, $container_guid = null, $ $time_created = (int) $time_created; } + if ($access_id == ACCESS_DEFAULT) { + throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); + } + if ($entity && $entity->canEdit()) { if (elgg_trigger_event('update', $entity->type, $entity)) { $ret = update_data("UPDATE {$CONFIG->dbprefix}entities @@ -493,6 +552,7 @@ function can_write_to_container($user_guid = 0, $container_guid = 0, $type = 'al // If still not approved, see if the user is a member of the group // @todo this should be moved to the groups plugin/library if (!$return && $user && $container instanceof ElggGroup) { + /* @var ElggGroup $container */ if ($container->isMember($user)) { $return = true; } @@ -542,7 +602,6 @@ $container_guid = 0) { $type = sanitise_string($type); $subtype_id = add_subtype($type, $subtype); $owner_guid = (int)$owner_guid; - $access_id = (int)$access_id; $time = time(); if ($site_guid == 0) { $site_guid = $CONFIG->site_guid; @@ -551,6 +610,10 @@ $container_guid = 0) { if ($container_guid == 0) { $container_guid = $owner_guid; } + $access_id = (int)$access_id; + if ($access_id == ACCESS_DEFAULT) { + throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); + } $user_guid = elgg_get_logged_in_user_guid(); if (!can_write_to_container($user_guid, $owner_guid, $type, $subtype)) { @@ -698,7 +761,7 @@ function get_entity($guid) { // @todo We need a single Memcache instance with a shared pool of namespace wrappers. This function would pull an instance from the pool. static $shared_cache; - // We could also use: if (!(int) $guid) { return FALSE }, + // We could also use: if (!(int) $guid) { return FALSE }, // but that evaluates to a false positive for $guid = TRUE. // This is a bit slower, but more thorough. if (!is_numeric($guid) || $guid === 0 || $guid === '0') { @@ -706,7 +769,7 @@ function get_entity($guid) { } // Check local cache first - $new_entity = retrieve_cached_entity($guid); + $new_entity = _elgg_retrieve_cached_entity($guid); if ($new_entity) { return $new_entity; } @@ -728,16 +791,22 @@ function get_entity($guid) { if ($shared_cache) { $cached_entity = $shared_cache->load($guid); - // @todo store ACLs in memcache http://trac.elgg.org/ticket/3018#comment:3 + // @todo store ACLs in memcache https://github.com/elgg/elgg/issues/3018#issuecomment-13662617 if ($cached_entity) { // @todo use ACL and cached entity access_id to determine if user can see it return $cached_entity; } } - $new_entity = entity_row_to_elggstar($entity_row); + // 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); + _elgg_cache_entity($new_entity); } return $new_entity; } @@ -864,6 +933,8 @@ function elgg_get_entities(array $options = array()) { 'joins' => array(), 'callback' => 'entity_row_to_elggstar', + + '__ElggBatch' => null, ); $options = array_merge($defaults, $options); @@ -980,14 +1051,19 @@ 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, $options['__ElggBatch']); + } else { + $dt = get_data($query, $options['callback']); + } + if ($dt) { // populate entity and metadata caches $guids = array(); foreach ($dt as $item) { // A custom callback could result in items that aren't ElggEntity's, so check for them if ($item instanceof ElggEntity) { - cache_entity($item); + _elgg_cache_entity($item); // plugins usually have only settings if (!$item instanceof ElggPlugin) { $guids[] = $item->guid; @@ -1009,6 +1085,104 @@ function elgg_get_entities(array $options = array()) { } /** + * Return entities from an SQL query generated by elgg_get_entities. + * + * @param string $sql + * @param ElggBatch $batch + * @return ElggEntity[] + * + * @access private + * @throws LogicException + */ +function _elgg_fetch_entities_from_sql($sql, ElggBatch $batch = null) { + 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 = _elgg_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]); + + // report incompletes to the batch process that spawned this query + if ($batch) { + $batch->reportIncompleteEntity($row); + } + } + } + } + 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 @@ -1077,13 +1251,24 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair $subtype_ids = array(); if ($subtypes) { foreach ($subtypes as $subtype) { - // check that the subtype is valid (with ELGG_ENTITIES_NO_VALUE being a valid subtype) - if (ELGG_ENTITIES_NO_VALUE === $subtype || $subtype_id = get_subtype_id($type, $subtype)) { - $subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $subtype) ? ELGG_ENTITIES_NO_VALUE : $subtype_id; - } else { - $valid_subtypes_count--; - elgg_log("Type-subtype '$type:$subtype' does not exist!", 'NOTICE'); + // check that the subtype is valid + if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) { + // subtype value is 0 + $subtype_ids[] = ELGG_ENTITIES_NO_VALUE; + } elseif (!$subtype) { + // subtype is ignored. + // this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0 continue; + } else { + $subtype_id = get_subtype_id($type, $subtype); + + if ($subtype_id) { + $subtype_ids[] = $subtype_id; + } else { + $valid_subtypes_count--; + elgg_log("Type-subtype '$type:$subtype' does not exist!", 'NOTICE'); + continue; + } } } @@ -1288,8 +1473,10 @@ function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entiti global $autofeed; $autofeed = true; + $offset_key = isset($options['offset_key']) ? $options['offset_key'] : 'offset'; + $defaults = array( - 'offset' => (int) max(get_input('offset', 0), 0), + 'offset' => (int) max(get_input($offset_key, 0), 0), 'limit' => (int) max(get_input('limit', 10), 0), 'full_view' => TRUE, 'list_type_toggle' => FALSE, @@ -1319,11 +1506,13 @@ function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entiti * * @tip Use this to generate a list of archives by month for when entities were added or updated. * + * @todo document how to pass in array for $subtype + * * @warning Months are returned in the form YYYYMM. * * @param string $type The type of entity * @param string $subtype The subtype of entity - * @param int $container_guid The container GUID that the entinties belong to + * @param int $container_guid The container GUID that the entities belong to * @param int $site_guid The site GUID * @param string $order_by Order_by SQL order by clause * @@ -1473,7 +1662,7 @@ function disable_entity($guid, $reason = "", $recursive = true) { $entity->disableMetadata(); $entity->disableAnnotations(); - invalidate_cache_for_entity($guid); + _elgg_invalidate_cache_for_entity($guid); $res = update_data("UPDATE {$CONFIG->dbprefix}entities SET enabled = 'no' @@ -1489,8 +1678,8 @@ function disable_entity($guid, $reason = "", $recursive = true) { /** * Enable an entity. * - * @warning In order to enable an entity using ElggEntity::enable(), - * you must first use {@link access_show_hidden_entities()}. + * @warning In order to enable an entity, you must first use + * {@link access_show_hidden_entities()}. * * @param int $guid GUID of entity to enable * @param bool $recursive Recursively enable all entities disabled with the entity? @@ -1571,7 +1760,7 @@ function delete_entity($guid, $recursive = true) { // delete cache if (isset($ENTITY_CACHE[$guid])) { - invalidate_cache_for_entity($guid); + _elgg_invalidate_cache_for_entity($guid); } // If memcache is available then delete this entry from the cache @@ -1618,6 +1807,10 @@ function delete_entity($guid, $recursive = true) { elgg_set_ignore_access($ia); } + $entity_disable_override = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $ia = elgg_set_ignore_access(true); + // Now delete the entity itself $entity->deleteMetadata(); $entity->deleteOwnedMetadata(); @@ -1625,6 +1818,9 @@ function delete_entity($guid, $recursive = true) { $entity->deleteOwnedAnnotations(); $entity->deleteRelationships(); + access_show_hidden_entities($entity_disable_override); + elgg_set_ignore_access($ia); + elgg_delete_river(array('subject_guid' => $guid)); elgg_delete_river(array('object_guid' => $guid)); remove_all_private_settings($guid); @@ -1772,7 +1968,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(); @@ -1838,7 +2034,7 @@ function oddentity_to_elggentity(ODDEntity $element) { 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); @@ -1932,7 +2128,7 @@ function can_edit_entity_metadata($entity_guid, $user_guid = 0, $metadata = null $return = null; - if ($metadata->owner_guid == 0) { + if ($metadata && ($metadata->owner_guid == 0)) { $return = true; } if (is_null($return)) { @@ -2011,7 +2207,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; } @@ -2273,6 +2469,7 @@ function elgg_instanceof($entity, $type = NULL, $subtype = NULL, $class = NULL) $return = ($entity instanceof ElggEntity); if ($type) { + /* @var ElggEntity $entity */ $return = $return && ($entity->getType() == $type); } @@ -2332,11 +2529,18 @@ function update_entity_last_action($guid, $posted = NULL) { function entities_gc() { global $CONFIG; - $tables = array ('sites_entity', 'objects_entity', 'groups_entity', 'users_entity'); + $tables = array( + 'site' => 'sites_entity', + 'object' => 'objects_entity', + 'group' => 'groups_entity', + 'user' => 'users_entity' + ); - foreach ($tables as $table) { - delete_data("DELETE from {$CONFIG->dbprefix}{$table} - where guid NOT IN (SELECT guid from {$CONFIG->dbprefix}entities)"); + foreach ($tables as $type => $table) { + delete_data("DELETE FROM {$CONFIG->dbprefix}{$table} + WHERE guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}entities)"); + delete_data("DELETE FROM {$CONFIG->dbprefix}entities + WHERE type = '$type' AND guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}{$table})"); } } diff --git a/engine/lib/export.php b/engine/lib/export.php index ae9be95ce..ecc894e63 100644 --- a/engine/lib/export.php +++ b/engine/lib/export.php @@ -11,7 +11,7 @@ * * @param mixed $object The object either an ElggEntity, ElggRelationship or ElggExtender * - * @return the UUID or false + * @return string|false the UUID or false */ function get_uuid_from_object($object) { if ($object instanceof ElggEntity) { @@ -40,8 +40,6 @@ function get_uuid_from_object($object) { * @return string */ function guid_to_uuid($guid) { - global $CONFIG; - return elgg_get_site_url() . "export/opendd/$guid/"; } @@ -53,8 +51,6 @@ function guid_to_uuid($guid) { * @return bool */ function is_uuid_this_domain($uuid) { - global $CONFIG; - if (strpos($uuid, elgg_get_site_url()) === 0) { return true; } @@ -67,7 +63,7 @@ function is_uuid_this_domain($uuid) { * * @param string $uuid A unique ID * - * @return mixed + * @return ElggEntity|false */ function get_entity_from_uuid($uuid) { $uuid = sanitise_string($uuid); @@ -117,18 +113,19 @@ function _process_element(ODD $odd) { global $IMPORTED_DATA, $IMPORTED_OBJECT_COUNTER; // See if anyone handles this element, return true if it is. + $to_be_serialised = null; if ($odd) { $handled = elgg_trigger_plugin_hook("import", "all", array("element" => $odd), $to_be_serialised); - } - // If not, then see if any of its sub elements are handled - if ($handled) { - // Increment validation counter - $IMPORTED_OBJECT_COUNTER ++; - // Return the constructed object - $IMPORTED_DATA[] = $handled; + // If not, then see if any of its sub elements are handled + if ($handled) { + // Increment validation counter + $IMPORTED_OBJECT_COUNTER ++; + // Return the constructed object + $IMPORTED_DATA[] = $handled; - return true; + return true; + } } return false; @@ -167,7 +164,7 @@ function exportAsArray($guid) { * * @param int $guid The GUID. * - * @return xml + * @return string XML * @see ElggEntity for an example of its usage. * @access private */ @@ -184,7 +181,7 @@ function export($guid) { * @param string $xml XML string * * @return bool - * @throws Exception if there was a problem importing the data. + * @throws ImportException if there was a problem importing the data. * @access private */ function import($xml) { diff --git a/engine/lib/extender.php b/engine/lib/extender.php index 43421342c..8323bd3ce 100644 --- a/engine/lib/extender.php +++ b/engine/lib/extender.php @@ -86,6 +86,7 @@ function oddmetadata_to_elggextender(ElggEntity $entity, ODDMetaData $element) { * @return null * @elgg_plugin_hook_handler volatile metadata * @todo investigate more. + * @throws ImportException * @access private */ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) { @@ -94,6 +95,7 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) $tmp = NULL; if ($element instanceof ODDMetaData) { + /* @var ODDMetaData $element */ // Recall entity $entity_uuid = $element->getAttribute('entity_uuid'); $entity = get_entity_from_uuid($entity_uuid); @@ -124,38 +126,45 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) * @return bool */ function can_edit_extender($extender_id, $type, $user_guid = 0) { - if (!elgg_is_logged_in()) { - return false; + // @todo Since Elgg 1.0, Elgg has returned false from can_edit_extender() + // if no user was logged in. This breaks the access override. This is a + // temporary work around. This function needs to be rewritten in Elgg 1.9 + if (!elgg_check_access_overrides($user_guid)) { + if (!elgg_is_logged_in()) { + return false; + } } $user_guid = (int)$user_guid; - $user = get_entity($user_guid); + $user = get_user($user_guid); if (!$user) { $user = elgg_get_logged_in_user_entity(); + $user_guid = elgg_get_logged_in_user_guid(); } $functionname = "elgg_get_{$type}_from_id"; 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()) { + if ($extender->getOwnerGUID() == $user_guid) { return true; } // If the user can edit the entity this is attached to, great! They can edit. - if (can_edit_entity($extender->entity_guid, $user->getGUID())) { + if (can_edit_entity($extender->entity_guid, $user_guid)) { return true; } - // Trigger plugin hooks + // Trigger plugin hook - note that $user may be null $params = array('entity' => $extender->getEntity(), 'user' => $user); return elgg_trigger_plugin_hook('permissions_check', $type, $params, false); } @@ -175,7 +184,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 +237,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/filestore.php b/engine/lib/filestore.php index 93a127257..a3c7ba439 100644 --- a/engine/lib/filestore.php +++ b/engine/lib/filestore.php @@ -308,8 +308,6 @@ function get_image_resize_parameters($width, $height, $options) { function file_delete($guid) { if ($file = get_entity($guid)) { if ($file->canEdit()) { - $container = get_entity($file->container_guid); - $thumbnail = $file->thumbnail; $smallthumb = $file->smallthumb; $largethumb = $file->largethumb; @@ -383,7 +381,7 @@ function file_get_general_file_type($mimetype) { /** * Delete a directory and all its contents * - * @param str $directory Directory to delete + * @param string $directory Directory to delete * * @return bool */ @@ -500,7 +498,7 @@ function filestore_init() { /** * Unit tests for files * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params diff --git a/engine/lib/group.php b/engine/lib/group.php index feb1f1e7f..6ded8a825 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; @@ -169,7 +170,7 @@ function get_group_members($group_guid, $limit = 10, $offset = 0, $site_guid = 0 'relationship' => 'member', 'relationship_guid' => $group_guid, 'inverse_relationship' => TRUE, - 'types' => 'user', + 'type' => 'user', 'limit' => $limit, 'offset' => $offset, 'count' => $count, @@ -239,56 +240,52 @@ function leave_group($group_guid, $user_guid) { */ function get_users_membership($user_guid) { $options = array( + 'type' => 'group', 'relationship' => 'member', 'relationship_guid' => $user_guid, - 'inverse_relationship' => FALSE + 'inverse_relationship' => false, + 'limit' => false, ); return elgg_get_entities_from_relationship($options); } /** - * 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/input.php b/engine/lib/input.php index 6d1646e1a..80b0b8766 100644 --- a/engine/lib/input.php +++ b/engine/lib/input.php @@ -60,8 +60,8 @@ function get_input($variable, $default = NULL, $filter_result = TRUE) { * * Note: this function does not handle nested arrays (ex: form input of param[m][n]) * - * @param string $variable The name of the variable - * @param string $value The value of the variable + * @param string $variable The name of the variable + * @param string|string[] $value The value of the variable * * @return void */ @@ -226,6 +226,8 @@ function elgg_clear_sticky_value($form_name, $variable) { /** * Page handler for autocomplete endpoint. * + * @todo split this into functions/objects, this is way too big + * * /livesearch?q=<query> * * Other options include: @@ -233,6 +235,7 @@ function elgg_clear_sticky_value($form_name, $variable) { * match_owner int 0/1 * limit int default is 10 * + * @param array $page * @return string JSON string is returned and then exit * @access private */ @@ -265,10 +268,8 @@ function input_livesearch_page_handler($page) { } if (get_input('match_owner', false)) { - $owner_guid = $user->getGUID(); $owner_where = 'AND e.owner_guid = ' . $user->getGUID(); } else { - $owner_guid = null; $owner_where = ''; } @@ -289,7 +290,9 @@ function input_livesearch_page_handler($page) { if ($entities = get_data($query)) { foreach ($entities as $entity) { + // @todo use elgg_get_entities (don't query in a loop!) $entity = get_entity($entity->guid); + /* @var ElggUser $entity */ if (!$entity) { continue; } @@ -338,7 +341,9 @@ function input_livesearch_page_handler($page) { "; if ($entities = get_data($query)) { foreach ($entities as $entity) { + // @todo use elgg_get_entities (don't query in a loop!) $entity = get_entity($entity->guid); + /* @var ElggGroup $entity */ if (!$entity) { continue; } @@ -385,7 +390,9 @@ function input_livesearch_page_handler($page) { if ($entities = get_data($query)) { foreach ($entities as $entity) { + // @todo use elgg_get_entities (don't query in a loop!) $entity = get_entity($entity->guid); + /* @var ElggUser $entity */ if (!$entity) { continue; } diff --git a/engine/lib/languages.php b/engine/lib/languages.php index 98006f7cd..61ba91ddb 100644 --- a/engine/lib/languages.php +++ b/engine/lib/languages.php @@ -77,7 +77,7 @@ function elgg_echo($message_key, $args = array(), $language = "") { * @param string $country_code Standard country code (eg 'en', 'nl', 'es') * @param array $language_array Formatted array of strings * - * @return true|false Depending on success + * @return bool Depending on success */ function add_translation($country_code, $language_array) { global $CONFIG; @@ -104,8 +104,6 @@ function add_translation($country_code, $language_array) { * @return string The language code for the site/user or "en" if not set */ function get_current_language() { - global $CONFIG; - $language = get_language(); if (!$language) { @@ -141,6 +139,9 @@ function get_language() { return false; } +/** + * @access private + */ function _elgg_load_translations() { global $CONFIG; @@ -148,7 +149,7 @@ function _elgg_load_translations() { $loaded = true; $languages = array_unique(array('en', get_current_language())); foreach ($languages as $language) { - $data = elgg_load_system_cache("$language.php"); + $data = elgg_load_system_cache("$language.lang"); if ($data) { add_translation($language, unserialize($data)); } else { @@ -177,7 +178,7 @@ function _elgg_load_translations() { * @param bool $load_all If true all languages are loaded, if * false only the current language + en are loaded * - * @return void + * @return bool success */ function register_translations($path, $load_all = false) { global $CONFIG; @@ -229,23 +230,37 @@ function register_translations($path, $load_all = false) { /** * Reload all translations from all registered paths. * - * This is only called by functions which need to know all possible translations, namely the - * statistic gathering ones. + * This is only called by functions which need to know all possible translations. * * @todo Better on demand loading based on language_paths array * - * @return bool + * @return void */ function reload_all_translations() { global $CONFIG; static $LANG_RELOAD_ALL_RUN; if ($LANG_RELOAD_ALL_RUN) { - return null; + return; } - foreach ($CONFIG->language_paths as $path => $dummy) { - register_translations($path, true); + if ($CONFIG->i18n_loaded_from_cache) { + $cache = elgg_get_system_cache(); + $cache_dir = $cache->getVariable("cache_path"); + $filenames = elgg_get_file_list($cache_dir, array(), array(), array(".lang")); + foreach ($filenames as $filename) { + if (preg_match('/([a-z]+)\.[^.]+$/', $filename, $matches)) { + $language = $matches[1]; + $data = elgg_load_system_cache("$language.lang"); + if ($data) { + add_translation($language, unserialize($data)); + } + } + } + } else { + foreach ($CONFIG->language_paths as $path => $dummy) { + register_translations($path, true); + } } $LANG_RELOAD_ALL_RUN = true; @@ -337,14 +352,3 @@ function get_missing_language_keys($language) { return false; } - -/** - * Initialize the language library - * @access private - */ -function elgg_languages_init() { - $lang = get_current_language(); - elgg_register_simplecache_view("js/languages/$lang"); -} - -elgg_register_event_handler('init', 'system', 'elgg_languages_init'); diff --git a/engine/lib/location.php b/engine/lib/location.php index 5b889509b..1534c7d7b 100644 --- a/engine/lib/location.php +++ b/engine/lib/location.php @@ -101,7 +101,7 @@ function elgg_get_entities_from_location(array $options = array()) { $long_min = $long - $long_distance; $long_max = $long + $long_distance; - $where = array(); + $wheres = array(); $wheres[] = "lat_name.string='geo:lat'"; $wheres[] = "lat_value.string >= $lat_min"; $wheres[] = "lat_value.string <= $lat_max"; @@ -139,7 +139,7 @@ function elgg_get_entities_from_location(array $options = array()) { /** * Returns a viewable list of entities from location * - * @param array $options + * @param array $options Options array * * @see elgg_list_entities() * @see elgg_get_entities_from_location() diff --git a/engine/lib/mb_wrapper.php b/engine/lib/mb_wrapper.php index c2f5503e0..68fa69005 100644 --- a/engine/lib/mb_wrapper.php +++ b/engine/lib/mb_wrapper.php @@ -11,7 +11,7 @@ if (is_callable('mb_internal_encoding')) { * NOTE: This differs from parse_str() by returning the results * instead of placing them in the local scope! * - * @param str $str The string + * @param string $str The string * * @return array * @since 1.7.0 diff --git a/engine/lib/memcache.php b/engine/lib/memcache.php index f79fba4a9..79b87e850 100644 --- a/engine/lib/memcache.php +++ b/engine/lib/memcache.php @@ -35,3 +35,23 @@ function is_memcache_available() { return $memcache_available; } + +/** + * Invalidate an entity in memcache + * + * @param int $entity_guid The GUID of the entity to invalidate + * + * @return void + * @access private + */ +function _elgg_invalidate_memcache_for_entity($entity_guid) { + static $newentity_cache; +
+ if ((!$newentity_cache) && (is_memcache_available())) {
+ $newentity_cache = new ElggMemcache('new_entity_cache');
+ } +
+ if ($newentity_cache) {
+ $newentity_cache->delete($entity_guid);
+ } +}
\ No newline at end of file diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php index f76c20f24..fdb1b85f6 100644 --- a/engine/lib/metadata.php +++ b/engine/lib/metadata.php @@ -191,19 +191,19 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i } // Add the metastring - $value = add_metastring($value); - if (!$value) { + $value_id = add_metastring($value); + if (!$value_id) { return false; } - $name = add_metastring($name); - if (!$name) { + $name_id = add_metastring($name); + if (!$name_id) { return false; } // If ok then add it $query = "UPDATE {$CONFIG->dbprefix}metadata" - . " set name_id='$name', value_id='$value', value_type='$value_type', access_id=$access_id," + . " set name_id='$name_id', value_id='$value_id', value_type='$value_type', access_id=$access_id," . " owner_guid=$owner_guid where id=$id"; $result = update_data($query); @@ -277,10 +277,18 @@ $access_id = ACCESS_PRIVATE, $allow_multiple = false) { * all metadata that match the query instead of returning * ElggMetadata objects. * - * @return mixed + * @return ElggMetadata[]|mixed * @since 1.8.0 */ function elgg_get_metadata(array $options = array()) { + + // @todo remove support for count shortcut - see #4393 + // support shortcut of 'count' => true for 'metadata_calculation' => 'count' + if (isset($options['count']) && $options['count']) { + $options['metadata_calculation'] = 'count'; + unset($options['count']); + } + $options['metastring_type'] = 'metadata'; return elgg_get_metastring_based_objects($options); } @@ -292,21 +300,22 @@ function elgg_get_metadata(array $options = array()) { * This requires at least one constraint: metadata_owner_guid(s), * metadata_name(s), metadata_value(s), or guid(s) must be set. * - * @warning This returns null on no ops. - * * @param array $options An options array. {@see elgg_get_metadata()} - * @return mixed Null if the metadata name is invalid. Bool on success or fail. + * @return bool|null true on success, false on failure, null if no metadata to delete. * @since 1.8.0 */ function elgg_delete_metadata(array $options) { if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) { return false; } + $options['metastring_type'] = 'metadata'; + $result = elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); + // This moved last in case an object's constructor sets metadata. Currently the batch + // delete process has to create the entity to delete its metadata. See #5214 elgg_get_metadata_cache()->invalidateByOptions('delete', $options); - $options['metastring_type'] = 'metadata'; - return elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); + return $result; } /** @@ -314,10 +323,8 @@ function elgg_delete_metadata(array $options) { * * @warning Unlike elgg_get_metadata() this will not accept an empty options array! * - * @warning This returns null on no ops. - * * @param array $options An options array. {@See elgg_get_metadata()} - * @return mixed + * @return bool|null true on success, false on failure, null if no metadata disabled. * @since 1.8.0 */ function elgg_disable_metadata(array $options) { @@ -326,9 +333,13 @@ function elgg_disable_metadata(array $options) { } elgg_get_metadata_cache()->invalidateByOptions('disable', $options); + + // if we can see hidden (disabled) we need to use the offset + // otherwise we risk an infinite loop if there are more than 50 + $inc_offset = access_get_show_hidden_status(); $options['metastring_type'] = 'metadata'; - return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', false); + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset); } /** @@ -336,10 +347,11 @@ function elgg_disable_metadata(array $options) { * * @warning Unlike elgg_get_metadata() this will not accept an empty options array! * - * @warning This returns null on no ops. + * @warning In order to enable metadata, you must first use + * {@link access_show_hidden_entities()}. * * @param array $options An options array. {@See elgg_get_metadata()} - * @return mixed + * @return bool|null true on success, false on failure, null if no metadata enabled. * @since 1.8.0 */ function elgg_enable_metadata(array $options) { @@ -394,9 +406,11 @@ function elgg_enable_metadata(array $options) { * 'operand' => '=', * 'case_sensitive' => TRUE * ) - * Currently if multiple values are sent via + * Currently if multiple values are sent via * an array (value => array('value1', 'value2') * the pair's operand will be forced to "IN". + * If passing "IN" as the operand and a string as the value, + * the value must be a properly quoted and escaped string. * * metadata_name_value_pairs_operator => NULL|STR The operator to use for combining * (name = value) OPERATOR (name = value); default AND @@ -412,7 +426,7 @@ function elgg_enable_metadata(array $options) { * * metadata_owner_guids => NULL|ARR guids for metadata owners * - * @return mixed If count, int. If not count, array. false on errors. + * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors. * @since 1.7.0 */ function elgg_get_entities_from_metadata(array $options = array()) { @@ -461,7 +475,7 @@ function elgg_get_entities_from_metadata(array $options = array()) { * @param array|null $order_by_metadata Array of names / direction * @param array|null $owner_guids Array of owner GUIDs * - * @return FALSE|array False on fail, array('joins', 'wheres') + * @return false|array False on fail, array('joins', 'wheres') * @since 1.7.0 * @access private */ @@ -608,6 +622,8 @@ $owner_guids = NULL) { // if the operand is IN don't quote it because quoting should be done already. if (is_numeric($pair['value'])) { $value = sanitise_string($pair['value']); + } else if (is_bool($pair['value'])) { + $value = (int) $pair['value']; } else if (is_array($pair['value'])) { $values_array = array(); @@ -774,10 +790,10 @@ function string_to_tag_array($string) { $ar = explode(",", $string); $ar = array_map('trim', $ar); $ar = array_filter($ar, 'is_not_null'); + $ar = array_map('strip_tags', $ar); return $ar; } return false; - } /** @@ -909,8 +925,8 @@ function elgg_get_metadata_cache() { * Invalidate the metadata cache based on options passed to various *_metadata functions * * @param string $action Action performed on metadata. "delete", "disable", or "enable" - * - * @param array $options Options passed to elgg_(delete|disable|enable)_metadata + * @param array $options Options passed to elgg_(delete|disable|enable)_metadata + * @return void */ function elgg_invalidate_metadata_cache($action, array $options) { // remove as little as possible, optimizing for common cases diff --git a/engine/lib/metastrings.php b/engine/lib/metastrings.php index cf6dd4d98..57d876c06 100644 --- a/engine/lib/metastrings.php +++ b/engine/lib/metastrings.php @@ -67,7 +67,7 @@ function get_metastring_id($string, $case_sensitive = TRUE) { } $row = FALSE; - $metaStrings = get_data($query, "entity_row_to_elggstar"); + $metaStrings = get_data($query); if (is_array($metaStrings)) { if (sizeof($metaStrings) > 1) { $ids = array(); @@ -389,11 +389,6 @@ function elgg_get_metastring_based_objects($options) { $selects = $options['selects']; - // allow count shortcut - if ($options['count']) { - $options['metastring_calculation'] = 'count'; - } - // For performance reasons we don't want the joins required for metadata / annotations // unless we're going through one of their callbacks. // this means we expect the functions passing different callbacks to pass their required joins. @@ -426,9 +421,11 @@ function elgg_get_metastring_based_objects($options) { if ($metastring_clauses) { $wheres = array_merge($wheres, $metastring_clauses['wheres']); $joins = array_merge($joins, $metastring_clauses['joins']); + } else { + $wheres[] = get_access_sql_suffix('n_table'); } - if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE) { + if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) { $selects = array_unique($selects); // evalutate selects $select_str = ''; @@ -439,6 +436,9 @@ function elgg_get_metastring_based_objects($options) { } $query = "SELECT DISTINCT n_table.*{$select_str} FROM {$db_prefix}$type n_table"; + } elseif ($options['count']) { + // count is over the entities + $query = "SELECT count(DISTINCT e.guid) as calculation FROM {$db_prefix}$type n_table"; } else { $query = "SELECT {$options['metastring_calculation']}(v.string) as calculation FROM {$db_prefix}$type n_table"; } @@ -467,7 +467,7 @@ function elgg_get_metastring_based_objects($options) { $defaults['order_by']); } - if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE) { + if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) { if (isset($options['group_by'])) { $options['group_by'] = sanitise_string($options['group_by']); $query .= " GROUP BY {$options['group_by']}"; @@ -515,21 +515,16 @@ function elgg_get_metastring_sql($table, $names = null, $values = null, && !$ids && (!$pairs && $pairs !== 0)) { - return ''; + return array(); } $db_prefix = elgg_get_config('dbprefix'); - // join counter for incremental joins. - $i = 1; - // binary forces byte-to-byte comparision of strings, making // it case- and diacritical-mark- sensitive. // only supported on values. $binary = ($case_sensitive) ? ' BINARY ' : ''; - $access = get_access_sql_suffix($table); - $return = array ( 'joins' => array (), 'wheres' => array() @@ -594,13 +589,15 @@ function elgg_get_metastring_sql($table, $names = null, $values = null, } if ($names_where && $values_where) { - $wheres[] = "($names_where AND $values_where AND $access)"; + $wheres[] = "($names_where AND $values_where)"; } elseif ($names_where) { - $wheres[] = "($names_where AND $access)"; + $wheres[] = $names_where; } elseif ($values_where) { - $wheres[] = "($values_where AND $access)"; + $wheres[] = $values_where; } + $wheres[] = get_access_sql_suffix($table); + if ($where = implode(' AND ', $wheres)) { $return['wheres'][] = "($where)"; } @@ -663,9 +660,10 @@ function elgg_normalize_metastrings_options(array $options = array()) { * * @param int $id The object's ID * @param string $enabled Value to set to: yes or no - * @param string $type The type of table to use: metadata or anntations + * @param string $type The type of table to use: metadata or annotations * * @return bool + * @throws InvalidParameterException * @since 1.8.0 * @access private */ @@ -740,7 +738,7 @@ function elgg_batch_metastring_based_objects(array $options, $callback, $inc_off * * @param int $id The metastring-based object's ID * @param string $type The type: annotation or metadata - * @return mixed + * @return ElggMetadata|ElggAnnotation * * @since 1.8.0 * @access private @@ -806,6 +804,7 @@ function elgg_delete_metastring_based_object_by_id($id, $type) { } if ($metabyname_memcache) { + // @todo why name_id? is that even populated? $metabyname_memcache->delete("{$obj->entity_guid}:{$obj->name_id}"); } } diff --git a/engine/lib/navigation.php b/engine/lib/navigation.php index 8c3952594..ab9cc05e8 100644 --- a/engine/lib/navigation.php +++ b/engine/lib/navigation.php @@ -126,6 +126,7 @@ function elgg_unregister_menu_item($menu_name, $item_name) { } foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) { + /* @var ElggMenuItem $menu_object */ if ($menu_object->getName() == $item_name) { unset($CONFIG->menus[$menu_name][$index]); return true; @@ -151,7 +152,8 @@ function elgg_is_menu_item_registered($menu_name, $item_name) { return false; } - foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) { + foreach ($CONFIG->menus[$menu_name] as $menu_object) { + /* @var ElggMenuItem $menu_object */ if ($menu_object->getName() == $item_name) { return true; } @@ -216,7 +218,7 @@ function elgg_push_breadcrumb($title, $link = NULL) { } // avoid key collisions. - $CONFIG->breadcrumbs[] = array('title' => $title, 'link' => $link); + $CONFIG->breadcrumbs[] = array('title' => elgg_get_excerpt($title, 100), 'link' => $link); } /** @@ -308,6 +310,37 @@ 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) { + foreach ($section as $item) { + if ($item->getSelected()) { + $selected = true; + break 2; + } + } + } + + if (!$selected) { + // nothing selected, match name to context or match url + $current_url = current_page_url(); + foreach ($return as $section_name => $section) { + foreach ($section as $key => $item) { + // only highlight internal links + if (strpos($item->getHref(), elgg_get_site_url()) === 0) { + if ($item->getName() == elgg_get_context()) { + $return[$section_name][$key]->setSelected(true); + break 2; + } + if ($item->getHref() == $current_url) { + $return[$section_name][$key]->setSelected(true); + break 2; + } + } + } + } + } return $return; } @@ -319,6 +352,7 @@ function elgg_site_menu_setup($hook, $type, $return, $params) { function elgg_river_menu_setup($hook, $type, $return, $params) { if (elgg_is_logged_in()) { $item = $params['item']; + /* @var ElggRiverItem $item */ $object = $item->getObjectEntity(); // comments and non-objects cannot be commented on or liked if (!elgg_in_context('widgets') && $item->annotation_id == 0) { @@ -362,6 +396,7 @@ function elgg_entity_menu_setup($hook, $type, $return, $params) { } $entity = $params['entity']; + /* @var ElggEntity $entity */ $handler = elgg_extract('handler', $params, false); // access @@ -407,6 +442,7 @@ function elgg_entity_menu_setup($hook, $type, $return, $params) { function elgg_widget_menu_setup($hook, $type, $return, $params) { $widget = $params['entity']; + /* @var ElggWidget $widget */ $show_edit = elgg_extract('show_edit', $params, true); $collapse = array( @@ -455,6 +491,7 @@ function elgg_widget_menu_setup($hook, $type, $return, $params) { */ function elgg_annotation_menu_setup($hook, $type, $return, $params) { $annotation = $params['annotation']; + /* @var ElggAnnotation $annotation */ if ($annotation->name == 'generic_comment' && $annotation->canEdit()) { $url = elgg_http_add_url_query_elements('action/comments/delete', array( diff --git a/engine/lib/notification.php b/engine/lib/notification.php index 18faff27f..be0c359d4 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; @@ -86,7 +86,7 @@ function unregister_notification_handler($method) { * @throws NotificationException */ function notify_user($to, $from, $subject, $message, array $params = NULL, $methods_override = "") { - global $NOTIFICATION_HANDLERS, $CONFIG; + global $NOTIFICATION_HANDLERS; // Sanitise if (!is_array($to)) { @@ -110,12 +110,15 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth // Are we overriding delivery? $methods = $methods_override; if (!$methods) { - $tmp = (array)get_user_notification_settings($guid); + $tmp = get_user_notification_settings($guid); $methods = array(); - foreach ($tmp as $k => $v) { - // Add method if method is turned on for user! - if ($v) { - $methods[] = $k; + // $tmp may be false. don't cast + if (is_object($tmp)) { + foreach ($tmp as $k => $v) { + // Add method if method is turned on for user! + if ($v) { + $methods[] = $k; + } } } } @@ -131,8 +134,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 +144,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 @@ -164,7 +168,7 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth * * @param int $user_guid The user id * - * @return stdClass + * @return stdClass|false */ function get_user_notification_settings($user_guid = 0) { $user_guid = (int)$user_guid; @@ -173,7 +177,8 @@ function get_user_notification_settings($user_guid = 0) { $user_guid = elgg_get_logged_in_user_guid(); } - // @todo: holy crap, really? + // @todo: there should be a better way now that metadata is cached. E.g. just query for MD names, then + // query user object directly $all_metadata = elgg_get_metadata(array( 'guid' => $user_guid, 'limit' => 0 @@ -236,6 +241,7 @@ function set_user_notification_setting($user_guid, $method, $value) { * @param array $params Optional parameters (none taken in this instance) * * @return bool + * @throws NotificationException * @access private */ function email_notify_handler(ElggEntity $from, ElggUser $to, $subject, $message, @@ -262,7 +268,7 @@ array $params = NULL) { $to = $to->email; // From - $site = get_entity($CONFIG->site_guid); + $site = elgg_get_site_entity(); // If there's an email address, use it - but only if its not from a user. if (!($from instanceof ElggUser) && $from->email) { $from = $from->email; @@ -287,6 +293,7 @@ array $params = NULL) { * @param array $params Optional parameters (none used in this function) * * @return bool + * @throws NotificationException * @since 1.7.2 */ function elgg_send_email($from, $to, $subject, $body, array $params = NULL) { @@ -343,6 +350,8 @@ function elgg_send_email($from, $to, $subject, $body, array $params = NULL) { // Sanitise subject by stripping line endings $subject = preg_replace("/(\r\n|\r|\n)/", " ", $subject); + // this is because Elgg encodes everything and matches what is done with body + $subject = html_entity_decode($subject, ENT_COMPAT, 'UTF-8'); // Decode any html entities if (is_callable('mb_encode_mimeheader')) { $subject = mb_encode_mimeheader($subject, "UTF-8", "B"); } @@ -421,7 +430,7 @@ function register_notification_object($entity_type, $object_subtype, $language_n * @param int $user_guid The GUID of the user who wants to follow a user's content * @param int $author_guid The GUID of the user whose content the user wants to follow * - * @return true|false Depending on success + * @return bool Depending on success */ function register_notification_interest($user_guid, $author_guid) { return add_entity_relationship($user_guid, 'notify', $author_guid); @@ -433,7 +442,7 @@ function register_notification_interest($user_guid, $author_guid) { * @param int $user_guid The GUID of the user who is following a user's content * @param int $author_guid The GUID of the user whose content the user wants to unfollow * - * @return true|false Depending on success + * @return bool Depending on success */ function remove_notification_interest($user_guid, $author_guid) { return remove_entity_relationship($user_guid, 'notify', $author_guid); @@ -449,12 +458,13 @@ function remove_notification_interest($user_guid, $author_guid) { * @param string $object_type mixed * @param mixed $object The object created * - * @return void + * @return bool * @access private */ function object_notifications($event, $object_type, $object) { // We only want to trigger notification events for ElggEntities if ($object instanceof ElggEntity) { + /* @var ElggEntity $object */ // Get config data global $CONFIG, $SESSION, $NOTIFICATION_HANDLERS; @@ -491,9 +501,10 @@ function object_notifications($event, $object_type, $object) { 'relationship' => 'notify' . $method, 'relationship_guid' => $object->container_guid, 'inverse_relationship' => TRUE, - 'types' => 'user', - 'limit' => 99999 + 'type' => 'user', + 'limit' => false )); + /* @var ElggUser[] $interested_users */ if ($interested_users && is_array($interested_users)) { foreach ($interested_users as $user) { diff --git a/engine/lib/objects.php b/engine/lib/objects.php index f186c66cb..ff3cc733f 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; @@ -92,16 +93,16 @@ function get_object_sites($object_guid, $limit = 10, $offset = 0) { return elgg_get_entities_from_relationship(array( 'relationship' => 'member_of_site', 'relationship_guid' => $object_guid, - 'types' => 'site', + 'type' => 'site', 'limit' => $limit, - 'offset' => $offset + 'offset' => $offset, )); } /** * Runs unit tests for ElggObject * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params diff --git a/engine/lib/opendd.php b/engine/lib/opendd.php index f00ea6aab..7d635a295 100644 --- a/engine/lib/opendd.php +++ b/engine/lib/opendd.php @@ -7,6 +7,8 @@ * @version 0.4 */ +// @codingStandardsIgnoreStart + /** * Attempt to construct an ODD object out of a XmlElement or sub-elements. * @@ -103,3 +105,5 @@ function ODD_Import($xml) { function ODD_Export(ODDDocument $document) { return "$document"; } + +// @codingStandardsIgnoreEnd diff --git a/engine/lib/output.php b/engine/lib/output.php index 352de863b..de4f911fb 100644 --- a/engine/lib/output.php +++ b/engine/lib/output.php @@ -12,29 +12,34 @@ * * @param string $text The input string * - * @return string The output stirng with formatted links - **/ + * @return string The output string with formatted links + */ function parse_urls($text) { + + // URI specification: http://www.ietf.org/rfc/rfc3986.txt + // This varies from the specification in the following ways: + // * Supports non-ascii characters + // * Does not allow parentheses and single quotes + // * Cuts off commas, exclamation points, and periods off as last character + // @todo this causes problems with <attr = "val"> - // must be 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', - // - // we can put , in the list of excluded char but need to keep . because of domain names. - // it is removed in the callback. - $r = preg_replace_callback('/(?<!=)(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', + $r = preg_replace_callback('/(?<![=\/"\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\']+)/i', create_function( '$matches', ' $url = $matches[1]; - $period = \'\'; - if (substr($url, -1, 1) == \'.\') { - $period = \'.\'; - $url = trim($url, \'.\'); + $punc = ""; + $last = substr($url, -1, 1); + if (in_array($last, array(".", "!", ",", "(", ")"))) { + $punc = $last; + $url = rtrim($url, ".!,()"); } $urltext = str_replace("/", "/<wbr />", $url); - return "<a href=\"$url\">$urltext</a>$period"; + return "<a href=\"$url\" rel=\"nofollow\">$urltext</a>$punc"; ' ), $text); @@ -43,51 +48,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); } /** @@ -249,7 +229,6 @@ function elgg_normalize_url($url) { $php_5_3_0_to_5_3_2 = version_compare(PHP_VERSION, '5.3.0', '>=') && version_compare(PHP_VERSION, '5.3.3', '<'); - $validated = false; if ($php_5_2_13_and_below || $php_5_3_0_to_5_3_2) { $tmp_address = str_replace("-", "", $url); $validated = filter_var($tmp_address, FILTER_VALIDATE_URL); @@ -310,9 +289,9 @@ function elgg_get_friendly_title($title) { return $result; } - // handle some special cases - $title = str_replace('&', 'and', $title); - + // titles are often stored HTML encoded + $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8'); + $title = ElggTranslit::urlize($title); return $title; @@ -384,7 +363,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 * @@ -440,3 +419,51 @@ function _elgg_html_decode($string) { ); return $string; } + +/** + * Prepares query string for output to prevent CSRF attacks. + * + * @param string $string + * @return string + * + * @access private + */ +function _elgg_get_display_query($string) { + //encode <,>,&, quotes and characters above 127 + if (function_exists('mb_convert_encoding')) {
+ $display_query = mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
+ } else {
+ // if no mbstring extension, we just strip characters
+ $display_query = preg_replace("/[^\x01-\x7F]/", "", $string);
+ }
+ return htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false); +} + +/** + * Unit tests for Output + * + * @param string $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..bd63d08c6 100644 --- a/engine/lib/pageowner.php +++ b/engine/lib/pageowner.php @@ -29,7 +29,9 @@ function elgg_get_page_owner_guid($guid = 0) { // return guid of page owner entity $guid = elgg_trigger_plugin_hook('page_owner', 'system', NULL, 0); - $page_owner_guid = $guid; + if ($guid) { + $page_owner_guid = $guid; + } return $guid; } @@ -37,17 +39,23 @@ function elgg_get_page_owner_guid($guid = 0) { /** * Gets the owner entity for the current page. * - * @return ElggEntity|false The current page owner or false if none. + * @note Access is disabled when getting the page owner entity. + * + * @return ElggUser|ElggGroup|false The current page owner or false if none. * * @since 1.8.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 +83,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 +100,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,11 +109,13 @@ 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(); } } if ($user = get_user_by_username($username)) { + elgg_set_ignore_access($ia); return $user->getGUID(); } } @@ -109,6 +123,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 +145,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 +153,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 +161,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 +169,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..d5d3db466 100644 --- a/engine/lib/plugins.php +++ b/engine/lib/plugins.php @@ -91,7 +91,9 @@ function elgg_get_plugin_ids_in_dir($dir = null) { * @access private */ function elgg_generate_plugin_entities() { + // @todo $site unused, can remove? $site = get_config('site'); + $dir = elgg_get_plugins_path(); $db_prefix = elgg_get_config('dbprefix'); @@ -107,6 +109,7 @@ function elgg_generate_plugin_entities() { $old_access = access_get_show_hidden_status(); access_show_hidden_entities(true); $known_plugins = elgg_get_entities_from_relationship($options); + /* @var ElggPlugin[] $known_plugins */ if (!$known_plugins) { $known_plugins = array(); @@ -138,7 +141,7 @@ function elgg_generate_plugin_entities() { $index = $id_map[$plugin_id]; $plugin = $known_plugins[$index]; // was this plugin deleted and its entity disabled? - if ($plugin->enabled != 'yes') { + if (!$plugin->isEnabled()) { $plugin->enable(); $plugin->deactivate(); $plugin->setPriority('last'); @@ -176,13 +179,31 @@ 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. - * @return mixed ElggPlugin or false. + * @return ElggPlugin|false * @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 +211,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 ); @@ -241,6 +263,8 @@ function elgg_get_max_plugin_priority() { $data = get_data($q); if ($data) { $max = $data[0]->max; + } else { + $max = 1; } // can't have a priority of 0. @@ -287,13 +311,11 @@ function elgg_is_active_plugin($plugin_id, $site_guid = null) { * @access private */ function elgg_load_plugins() { - global $CONFIG; - $plugins_path = elgg_get_plugins_path(); - $start_flags = ELGG_PLUGIN_INCLUDE_START - | ELGG_PLUGIN_REGISTER_VIEWS - | ELGG_PLUGIN_REGISTER_LANGUAGES - | ELGG_PLUGIN_REGISTER_CLASSES; + $start_flags = ELGG_PLUGIN_INCLUDE_START | + ELGG_PLUGIN_REGISTER_VIEWS | + ELGG_PLUGIN_REGISTER_LANGUAGES | + ELGG_PLUGIN_REGISTER_CLASSES; if (!$plugins_path) { return false; @@ -341,7 +363,7 @@ function elgg_load_plugins() { * * @param string $status The status of the plugins. active, inactive, or all. * @param mixed $site_guid Optional site guid - * @return array + * @return ElggPlugin[] * @since 1.8.0 * @access private */ @@ -422,6 +444,7 @@ function elgg_set_plugin_priorities(array $order) { // though we do start with 1 $order = array_values($order); + $missing_plugins = array(); foreach ($plugins as $plugin) { $plugin_id = $plugin->getID(); @@ -512,6 +535,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) { @@ -618,19 +643,18 @@ function elgg_get_plugins_provides($type = null, $name = null) { * @access private */ function elgg_check_plugins_provides($type, $name, $version = null, $comparison = 'ge') { - if (!$provided = elgg_get_plugins_provides($type, $name)) { + $provided = elgg_get_plugins_provides($type, $name); + if (!$provided) { return array( 'status' => false, 'version' => '' ); } - if ($provided) { - if ($version) { - $status = version_compare($provided['version'], $version, $comparison); - } else { - $status = true; - } + if ($version) { + $status = version_compare($provided['version'], $version, $comparison); + } else { + $status = true; } return array( @@ -840,9 +864,9 @@ function elgg_set_plugin_user_setting($name, $value, $user_guid = null, $plugin_ /** * Unsets a user-specific plugin setting * - * @param str $name Name of the setting - * @param int $user_guid Defaults to logged in user - * @param str $plugin_id Defaults to contextual plugin name + * @param string $name Name of the setting + * @param int $user_guid Defaults to logged in user + * @param string $plugin_id Defaults to contextual plugin name * * @return bool * @since 1.8.0 @@ -920,6 +944,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) { @@ -1065,7 +1090,7 @@ function plugin_run_once() { /** * Runs unit tests for the entity objects. * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params @@ -1080,6 +1105,49 @@ function plugins_test($hook, $type, $value, $params) { } /** + * Checks on deactivate plugin event if disabling it won't create unmet dependencies and blocks disable in such case. + * + * @param string $event deactivate + * @param string $type plugin + * @param array $params Parameters array containing entry with ELggPlugin instance under 'plugin_entity' key + * @return bool false to block plugin deactivation action + * + * @access private + */ +function _plugins_deactivate_dependency_check($event, $type, $params) { + $plugin_id = $params['plugin_entity']->getManifest()->getPluginID(); + $plugin_name = $params['plugin_entity']->getManifest()->getName(); + + $active_plugins = elgg_get_plugins(); + + $dependents = array(); + foreach ($active_plugins as $plugin) { + $manifest = $plugin->getManifest(); + $requires = $manifest->getRequires(); + + foreach ($requires as $required) { + if ($required['type'] == 'plugin' && $required['name'] == $plugin_id) { + // there are active dependents + $dependents[$manifest->getPluginID()] = $plugin; + } + } + } + + if ($dependents) { + $list = '<ul>'; + // construct error message and prevent disabling + foreach ($dependents as $dependent) { + $list .= '<li>' . $dependent->getManifest()->getName() . '</li>'; + } + $list .= '</ul>'; + + register_error(elgg_echo('ElggPlugin:Dependencies:ActiveDependent', array($plugin_name, $list))); + + return false; + } +} + +/** * Initialize the plugin system * Listens to system init and registers actions * @@ -1090,6 +1158,10 @@ function plugin_init() { run_function_once("plugin_run_once"); elgg_register_plugin_hook_handler('unit_test', 'system', 'plugins_test'); + + // note - plugins are booted by the time this handler is registered + // deactivation due to error may have already occurred + elgg_register_event_handler('deactivate', 'plugin', '_plugins_deactivate_dependency_check'); elgg_register_action("plugins/settings/save", '', 'admin'); elgg_register_action("plugins/usersettings/save"); diff --git a/engine/lib/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 09d541e22..b0cd627fc 100644 --- a/engine/lib/relationships.php +++ b/engine/lib/relationships.php @@ -12,7 +12,7 @@ * * @param stdClass $row Database row from the relationship table * - * @return stdClass or ElggMetadata + * @return ElggRelationship|stdClass * @access private */ function row_to_elggrelationship($row) { @@ -28,7 +28,7 @@ function row_to_elggrelationship($row) { * * @param int $id The ID of a relationship * - * @return mixed + * @return ElggRelationship|false */ function get_relationship($id) { global $CONFIG; @@ -109,7 +109,7 @@ function add_entity_relationship($guid_one, $relationship, $guid_two) { * @param string $relationship The type of relationship * @param int $guid_two The GUID of the entity the relationship is with * - * @return object|false Depending on success + * @return ElggRelationship|false Depending on success */ function check_entity_relationship($guid_one, $relationship, $guid_two) { global $CONFIG; @@ -123,7 +123,7 @@ function check_entity_relationship($guid_one, $relationship, $guid_two) { AND relationship='$relationship' AND guid_two=$guid_two limit 1"; - $row = get_data_row($query); + $row = row_to_elggrelationship(get_data_row($query)); if ($row) { return $row; } @@ -220,7 +220,7 @@ function remove_entity_relationships($guid_one, $relationship = "", $inverse = f * @param int $guid The GUID of the relationship owner * @param bool $inverse_relationship Inverse relationship owners? * - * @return mixed + * @return ElggRelationship[] */ function get_entity_relationships($guid, $inverse_relationship = FALSE) { global $CONFIG; @@ -259,7 +259,7 @@ function get_entity_relationships($guid, $inverse_relationship = FALSE) { * * inverse_relationship => BOOL Inverse the relationship * - * @return mixed If count, int. If not count, array. false on errors. + * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors. * @since 1.7.0 */ function elgg_get_entities_from_relationship($options) { @@ -316,7 +316,7 @@ function elgg_get_entities_from_relationship($options) { * Provide in table.column format. * @param string $relationship Relationship string * @param int $relationship_guid Entity guid to check - * @param string $inverse_relationship Inverse relationship check? + * @param bool $inverse_relationship Inverse relationship check? * * @return mixed * @since 1.7.0 @@ -363,7 +363,7 @@ $relationship_guid = NULL, $inverse_relationship = FALSE) { /** * Returns a viewable list of entities by relationship * - * @param array $options + * @param array $options Options array for retrieval of entities * * @see elgg_list_entities() * @see elgg_get_entities_from_relationship() @@ -381,7 +381,7 @@ function elgg_list_entities_from_relationship(array $options = array()) { * * @param array $options An options array compatible with * elgg_get_entities_from_relationship() - * @return mixed int If count, int. If not count, array. false on errors. + * @return ElggEntity[]|mixed int If count, int. If not count, array. false on errors. * @since 1.8.0 */ function elgg_get_entities_from_relationship_count(array $options = array()) { @@ -398,7 +398,7 @@ function elgg_get_entities_from_relationship_count(array $options = array()) { * * @param array $options Options array * - * @return array + * @return string * @since 1.8.0 */ function elgg_list_entities_from_relationship_count($options) { @@ -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; } @@ -499,7 +499,7 @@ function already_attached($guid_one, $guid_two) { * @param int $guid Entity GUID * @param string $type The type of object to return e.g. 'file', 'friend_of' etc * - * @return an array of objects + * @return ElggEntity[] * @access private */ function get_attachments($guid, $type = "") { @@ -507,7 +507,7 @@ function get_attachments($guid, $type = "") { 'relationship' => 'attached', 'relationship_guid' => $guid, 'inverse_relationship' => false, - 'types' => $type, + 'type' => $type, 'subtypes' => '', 'owner_guid' => 0, 'order_by' => 'time_created desc', @@ -571,9 +571,8 @@ function import_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par if ($element instanceof ODDRelationship) { $tmp = new ElggRelationship(); $tmp->import($element); - - return $tmp; } + return $tmp; } /** @@ -586,11 +585,10 @@ function import_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par * * @elgg_event_handler export all * @return mixed + * @throws InvalidParameterException * @access private */ function export_relationship_plugin_hook($hook, $entity_type, $returnvalue, $params) { - global $CONFIG; - // Sanity check values if ((!is_array($params)) && (!isset($params['guid']))) { throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); @@ -624,9 +622,9 @@ function export_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par * @access private */ function relationship_notification_hook($event, $type, $object) { - + /* @var ElggRelationship $object */ $user_one = get_entity($object->guid_one); - $user_two = get_entity($object->guid_two); + /* @var ElggUser $user_one */ return notify_user($object->guid_two, $object->guid_one, diff --git a/engine/lib/river.php b/engine/lib/river.php index b717a7756..e92040eb7 100644 --- a/engine/lib/river.php +++ b/engine/lib/river.php @@ -120,7 +120,7 @@ $posted = 0, $annotation_id = 0) { * subtypes => STR|ARR Entity subtype string(s) * type_subtype_pairs => ARR Array of type => subtype pairs where subtype * can be an array of subtype strings - * + * * posted_time_lower => INT The lower bound on the time posted * posted_time_upper => INT The upper bound on the time posted * @@ -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 && !_elgg_retrieve_cached_entity($item->subject_guid)) { + $guids[$item->subject_guid] = true; + } + if ($item->object_guid && !_elgg_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 && !_elgg_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 */ @@ -388,8 +434,13 @@ function elgg_list_river(array $options = array()) { 'pagination' => TRUE, 'list_class' => 'elgg-list-river elgg-river', // @todo remove elgg-river in Elgg 1.9 ); - + $options = array_merge($defaults, $options); + + if (!$options["limit"] && !$options["offset"]) {
+ // no need for pagination if listing is unlimited
+ $options["pagination"] = false;
+ } $options['count'] = TRUE; $count = elgg_get_river($options); @@ -399,6 +450,7 @@ function elgg_list_river(array $options = array()) { $options['count'] = $count; $options['items'] = $items; + return elgg_view('page/components/list', $options); } @@ -454,6 +506,7 @@ function elgg_get_river_type_subtype_where_sql($table, $types, $subtypes, $pairs return ''; } + $wheres = array(); $types_wheres = array(); $subtypes_wheres = array(); @@ -598,7 +651,7 @@ function update_river_access_by_object($object_guid, $access_id) { } /** - * Page handler for activiy + * Page handler for activity * * @param array $page * @return bool @@ -617,10 +670,6 @@ function elgg_river_page_handler($page) { } set_input('page_type', $page_type); - // content filter code here - $entity_type = ''; - $entity_subtype = ''; - require_once("{$CONFIG->path}pages/river.php"); return true; } diff --git a/engine/lib/sessions.php b/engine/lib/sessions.php index 72ca0a1c2..e3d5ce9cd 100644 --- a/engine/lib/sessions.php +++ b/engine/lib/sessions.php @@ -87,6 +87,9 @@ function elgg_is_admin_logged_in() { */ function elgg_is_admin_user($user_guid) { global $CONFIG; + + $user_guid = (int)$user_guid; + // cannot use magic metadata here because of recursion // must support the old way of getting admin from metadata @@ -286,8 +289,6 @@ function check_rate_limit_exceeded($user_guid) { * @throws LoginException */ function login(ElggUser $user, $persistent = false) { - global $CONFIG; - // User is banned, return false. if ($user->isBanned()) { throw new LoginException(elgg_echo('LoginException:BannedUser')); @@ -325,6 +326,12 @@ function login(ElggUser $user, $persistent = false) { set_last_login($_SESSION['guid']); reset_login_failure_count($user->guid); // Reset any previous failed login attempts + // if memcache is enabled, invalidate the user in memcache @see https://github.com/Elgg/Elgg/issues/3143 + if (is_memcache_available()) { + // this needs to happen with a shutdown function because of the timing with set_last_login() + register_shutdown_function("_elgg_invalidate_memcache_for_entity", $_SESSION['guid']); + } + return true; } @@ -334,8 +341,6 @@ function login(ElggUser $user, $persistent = false) { * @return bool */ function logout() { - global $CONFIG; - if (isset($_SESSION['user'])) { if (!elgg_trigger_event('logout', 'user', $_SESSION['user'])) { return false; @@ -616,10 +621,8 @@ function _elgg_session_destroy($id) { global $sess_save_path; $sess_file = "$sess_save_path/sess_$id"; - return(@unlink($sess_file)); + return @unlink($sess_file); } - - return false; } /** diff --git a/engine/lib/sites.php b/engine/lib/sites.php index 8b772668d..3de0eccc2 100644 --- a/engine/lib/sites.php +++ b/engine/lib/sites.php @@ -26,7 +26,7 @@ function elgg_get_site_entity($site_guid = 0) { $site = get_entity($site_guid); } - if($site instanceof ElggSite){ + if ($site instanceof ElggSite) { $result = $site; } @@ -58,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; @@ -117,8 +118,6 @@ function create_site_entity($guid, $name, $description, $url) { * @return bool */ function add_site_user($site_guid, $user_guid) { - global $CONFIG; - $site_guid = (int)$site_guid; $user_guid = (int)$user_guid; @@ -149,8 +148,6 @@ function remove_site_user($site_guid, $user_guid) { * @return mixed */ function add_site_object($site_guid, $object_guid) { - global $CONFIG; - $site_guid = (int)$site_guid; $object_guid = (int)$object_guid; @@ -191,8 +188,8 @@ function get_site_objects($site_guid, $subtype = "", $limit = 10, $offset = 0) { 'relationship' => 'member_of_site', 'relationship_guid' => $site_guid, 'inverse_relationship' => TRUE, - 'types' => 'object', - 'subtypes' => $subtype, + 'type' => 'object', + 'subtype' => $subtype, 'limit' => $limit, 'offset' => $offset )); @@ -241,7 +238,7 @@ function get_site_domain($guid) { /** * Unit tests for sites * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params diff --git a/engine/lib/statistics.php b/engine/lib/statistics.php index 5ee640549..4cb0bb0b8 100644 --- a/engine/lib/statistics.php +++ b/engine/lib/statistics.php @@ -95,15 +95,20 @@ function get_number_users($show_deactivated = false) { * @return string */ function get_online_users() { - $count = find_active_users(600, 10, 0, true); - $objects = find_active_users(600, 10); + $limit = max(0, (int) get_input("limit", 10)); + $offset = max(0, (int) get_input("offset", 0)); + + $count = find_active_users(600, $limit, $offset, true); + $objects = find_active_users(600, $limit, $offset); if ($objects) { return elgg_view_entity_list($objects, array( 'count' => $count, - 'limit' => 10 + 'limit' => $limit, + 'offset' => $offset )); } + return ''; } /** diff --git a/engine/lib/system_log.php b/engine/lib/system_log.php index 53fa24557..84302632e 100644 --- a/engine/lib/system_log.php +++ b/engine/lib/system_log.php @@ -10,6 +10,8 @@ /** * Retrieve the system log based on a number of parameters. * + * @todo too many args, and the first arg is too confusing + * * @param int|array $by_user The guid(s) of the user(s) who initiated the event. * Use 0 for unowned entries. Anything else falsey means anyone. * @param string $event The event you are searching on. @@ -22,12 +24,12 @@ * @param int $timebefore Lower time limit * @param int $timeafter Upper time limit * @param int $object_id GUID of an object - * @param str $ip_address The IP address. + * @param string $ip_address The IP address. * @return mixed */ -function get_system_log($by_user = "", $event = "", $class = "", $type = "", $subtype = "", -$limit = 10, $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $object_id = 0, -$ip_address = false) { +function get_system_log($by_user = "", $event = "", $class = "", $type = "", $subtype = "", $limit = 10, + $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $object_id = 0, + $ip_address = "") { global $CONFIG; @@ -166,6 +168,7 @@ function system_log($object, $event) { if ($object instanceof Loggable) { + /* @var ElggEntity|ElggExtender $object */ if (datalist_get('version') < 2012012000) { // this is a site that doesn't have the ip_address column yet return; @@ -184,7 +187,16 @@ function system_log($object, $event) { $object_subtype = $object->getSubtype(); $event = sanitise_string($event); $time = time(); - $ip_address = sanitise_string($_SERVER['REMOTE_ADDR']); + + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip_address = array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])); + } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) { + $ip_address = array_pop(explode(',', $_SERVER['HTTP_X_REAL_IP'])); + } else { + $ip_address = $_SERVER['REMOTE_ADDR']; + } + $ip_address = sanitise_string($ip_address); + $performed_by = elgg_get_logged_in_user_guid(); if (isset($object->access_id)) { diff --git a/engine/lib/tags.php b/engine/lib/tags.php index a0887d0f3..586a9b9e4 100644 --- a/engine/lib/tags.php +++ b/engine/lib/tags.php @@ -48,7 +48,7 @@ function calculate_tag_size($min, $max, $number_of_tags, $buckets = 6) { * @param array $tags The array of tags. * @param int $buckets The number of buckets * - * @return An associated array of tags with a weighting, this can then be mapped to a display class. + * @return array An associated array of tags with a weighting, this can then be mapped to a display class. * @access private */ function generate_tag_cloud(array $tags, $buckets = 6) { @@ -114,8 +114,8 @@ function generate_tag_cloud(array $tags, $buckets = 6) { * * joins => array() Additional joins * - * @return false/array - if no tags or error, false - * otherwise, array of objects with ->tag and ->total values + * @return object[]|false If no tags or error, false + * otherwise, array of objects with ->tag and ->total values * @since 1.7.1 */ function elgg_get_tags(array $options = array()) { @@ -172,6 +172,7 @@ function elgg_get_tags(array $options = array()) { // catch for tags that were spaces $wheres[] = "msv.string != ''"; + $sanitised_tags = array(); foreach ($options['tag_names'] as $tag) { $sanitised_tags[] = '"' . sanitise_string($tag) . '"'; } diff --git a/engine/lib/upgrade.php b/engine/lib/upgrade.php index f0874a483..158ec9ec1 100644 --- a/engine/lib/upgrade.php +++ b/engine/lib/upgrade.php @@ -17,8 +17,9 @@ * @access private */ function upgrade_code($version, $quiet = FALSE) { + // do not remove - upgrade scripts depend on this global $CONFIG; - + $version = (int) $version; $upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/'; $processed_upgrades = elgg_get_processed_upgrades(); @@ -244,7 +245,7 @@ function version_upgrade() { // No version number? Oh snap...this is an upgrade from a clean installation < 1.7. // Run all upgrades without error reporting and hope for the best. - // See http://trac.elgg.org/elgg/ticket/1432 for more. + // See https://github.com/elgg/elgg/issues/1432 for more. $quiet = !$dbversion; // Note: Database upgrades are deprecated as of 1.8. Use code upgrades. See #1433 @@ -291,7 +292,6 @@ function elgg_upgrade_bootstrap_17_to_18() { '2011010101.php', ); - $upgrades_17 = array(); $upgrade_files = elgg_get_upgrade_files(); $processed_upgrades = array(); @@ -311,3 +311,55 @@ 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; + + $is_locked = count(get_data("show tables like '{$CONFIG->dbprefix}upgrade_lock'")); + + // @todo why? + _elgg_invalidate_query_cache(); + + return $is_locked; +} diff --git a/engine/lib/upgrades/2009102801.php b/engine/lib/upgrades/2009102801.php index cab9a6835..3ad113fb2 100644 --- a/engine/lib/upgrades/2009102801.php +++ b/engine/lib/upgrades/2009102801.php @@ -203,14 +203,15 @@ function user_file_matrix($guid) { return "$time_created/$user->guid/"; } -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE; +global $ENTITY_CACHE, $CONFIG; /** * Upgrade file locations */ $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity WHERE username != ''"); while ($user = mysql_fetch_object($users)) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); $to = $CONFIG->dataroot . user_file_matrix($user->guid); foreach (array('1_0', '1_1', '1_6') as $version) { diff --git a/engine/lib/upgrades/2010033101.php b/engine/lib/upgrades/2010033101.php index 0bffee001..4779295fd 100644 --- a/engine/lib/upgrades/2010033101.php +++ b/engine/lib/upgrades/2010033101.php @@ -1,7 +1,7 @@ <?php /** - * Conditional upgrade for UTF8 as described in http://trac.elgg.org/ticket/1928 + * Conditional upgrade for UTF8 as described in https://github.com/elgg/elgg/issues/1928 */ // get_version() returns the code version. diff --git a/engine/lib/upgrades/2010061501.php b/engine/lib/upgrades/2010061501.php index 9ff7d3102..744c28fd5 100644 --- a/engine/lib/upgrades/2010061501.php +++ b/engine/lib/upgrades/2010061501.php @@ -45,7 +45,7 @@ if ($dbversion < 2009100701) { } } - global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE; + global $ENTITY_CACHE; /** Upgrade file locations @@ -60,7 +60,9 @@ if ($dbversion < 2009100701) { $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity WHERE username != ''", $link); while ($user = mysql_fetch_object($users)) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); + $to = $CONFIG->dataroot . user_file_matrix($user->guid); foreach (array('1_0', '1_1', '1_6') as $version) { diff --git a/engine/lib/upgrades/2010071001.php b/engine/lib/upgrades/2010071001.php index 1b5d379d8..5594493a8 100644 --- a/engine/lib/upgrades/2010071001.php +++ b/engine/lib/upgrades/2010071001.php @@ -30,11 +30,12 @@ function user_file_matrix_2010071001($guid) { $sizes = array('large', 'medium', 'small', 'tiny', 'master', 'topbar'); -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +global $ENTITY_CACHE, $CONFIG; $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity WHERE username != ''"); while ($user = mysql_fetch_object($users)) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); $user_directory = user_file_matrix_2010071001($user->guid); if (!$user_directory) { diff --git a/engine/lib/upgrades/2010071002.php b/engine/lib/upgrades/2010071002.php index 30bd6538c..52aa15ef5 100644 --- a/engine/lib/upgrades/2010071002.php +++ b/engine/lib/upgrades/2010071002.php @@ -4,12 +4,13 @@ */ // loop through all users checking collections and notifications -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +global $ENTITY_CACHE, $CONFIG; global $NOTIFICATION_HANDLERS; $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity WHERE username != ''"); while ($user = mysql_fetch_object($users)) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); $user = get_entity($user->guid); foreach ($NOTIFICATION_HANDLERS as $method => $foo) { diff --git a/engine/lib/upgrades/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/upgrades/2011052801.php b/engine/lib/upgrades/2011052801.php index 8084bc06c..b5a8e1018 100644 --- a/engine/lib/upgrades/2011052801.php +++ b/engine/lib/upgrades/2011052801.php @@ -2,7 +2,7 @@ /** * Make sure all users have the relationship member_of_site */ -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +global $ENTITY_CACHE; $db_prefix = get_config('dbprefix'); $limit = 100; @@ -17,7 +17,8 @@ $q = "SELECT e.* FROM {$db_prefix}entities e $users = get_data($q); while ($users) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); // do manually to not trigger any events because these aren't new users. foreach ($users as $user) { diff --git a/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php index 07732f261..780038c32 100644 --- a/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php +++ b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php @@ -3,7 +3,7 @@ * Elgg 1.8.3 upgrade 2012041801 * multiple_user_tokens * - * Fixes http://trac.elgg.org/ticket/4291 + * Fixes https://github.com/elgg/elgg/issues/4291 * Removes the unique index on users_apisessions for user_guid and site_guid */ diff --git a/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php new file mode 100644 index 000000000..8eccf05e2 --- /dev/null +++ b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php @@ -0,0 +1,24 @@ +<?php +/** + * Elgg 1.8.14 upgrade 2013030600 + * update_user_location + * + * Before Elgg 1.8, a location like "London, England" would be stored as an array. + * This script turns that back into a string. + */ + +$ia = elgg_set_ignore_access(true); +$options = array( + 'type' => 'user', + 'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { + _elgg_invalidate_query_cache(); + + if (is_array($entity->location)) { + $entity->location = implode(', ', $entity->location); + } +} +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php new file mode 100644 index 000000000..ee99bdbc8 --- /dev/null +++ b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php @@ -0,0 +1,28 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013051700 + * add_missing_group_index + * + * Some Elgg sites are missing the groups_entity full text index on name and + * description. This checks if it exists and adds it if it does not. + */ + +$db_prefix = elgg_get_config('dbprefix'); + +$full_text_index_exists = false; +$results = get_data("SHOW INDEX FROM {$db_prefix}groups_entity"); +if ($results) { + foreach ($results as $result) { + if ($result->Index_type === 'FULLTEXT') { + $full_text_index_exists = true; + } + } +} + +if ($full_text_index_exists == false) { + $query = "ALTER TABLE {$db_prefix}groups_entity + ADD FULLTEXT name_2 (name, description)"; + if (!update_data($query)) { + elgg_log("Failed to add full text index to groups_entity table", 'ERROR'); + } +} diff --git a/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php new file mode 100644 index 000000000..d333a6cd2 --- /dev/null +++ b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013052900 + * ipv6_in_syslog + * + * Upgrade the ip column in system_log to be able to store ipv6 addresses + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}system_log MODIFY COLUMN ip_address varchar(46) NOT NULL"; + +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php new file mode 100644 index 000000000..538d74dd6 --- /dev/null +++ b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php @@ -0,0 +1,16 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013060900 + * site_secret + * + * Description + */ + +$strength = _elgg_get_site_secret_strength(); + +if ($strength !== 'strong') { + // a new key is needed immediately + register_translations(elgg_get_root_path() . 'languages/'); + + elgg_add_admin_notice('weak_site_key', elgg_echo("upgrade:site_secret_warning:$strength")); +} diff --git a/engine/lib/upgrades/create_upgrade.php b/engine/lib/upgrades/create_upgrade.php index 3652e18a2..b34f31b7e 100644 --- a/engine/lib/upgrades/create_upgrade.php +++ b/engine/lib/upgrades/create_upgrade.php @@ -93,7 +93,7 @@ if (!$h) { die("Could not open file $upgrade_file"); } -if (!fputs($h, $upgrade_code)) { +if (!fwrite($h, $upgrade_code)) { die("Could not write to $upgrade_file"); } else { elgg_set_version_dot_php_version($upgrade_version); @@ -128,8 +128,9 @@ function elgg_set_version_dot_php_version($version) { rewind($h); - fputs($h, $out); + fwrite($h, $out); fclose($h); + return true; } /** diff --git a/engine/lib/user_settings.php b/engine/lib/user_settings.php index e4069fb53..0e36dc46d 100644 --- a/engine/lib/user_settings.php +++ b/engine/lib/user_settings.php @@ -265,9 +265,9 @@ function elgg_set_user_default_access() { * @access private */ function usersettings_pagesetup() { - if (elgg_get_context() == "settings") { - $user = elgg_get_page_owner_entity(); + $user = elgg_get_page_owner_entity(); + if ($user && elgg_get_context() == "settings") { $params = array( 'name' => '1_account', 'text' => elgg_echo('usersettings:user:opt:linktext'), @@ -308,7 +308,7 @@ function usersettings_page_handler($page) { $user = get_user_by_username($page[1]); elgg_set_page_owner_guid($user->guid); } else { - $user = elgg_get_logged_in_user_guid(); + $user = elgg_get_logged_in_user_entity(); elgg_set_page_owner_guid($user->guid); } @@ -332,6 +332,7 @@ function usersettings_page_handler($page) { require $path; return true; } + return false; } /** diff --git a/engine/lib/users.php b/engine/lib/users.php index 527eff3cd..a8fb9121c 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; @@ -236,7 +237,7 @@ function make_user_admin($user_guid) { } $r = update_data("UPDATE {$CONFIG->dbprefix}users_entity set admin='yes' where guid=$user_guid"); - invalidate_cache_for_entity($user_guid); + _elgg_invalidate_cache_for_entity($user_guid); return $r; } @@ -272,7 +273,7 @@ function remove_user_admin($user_guid) { } $r = update_data("UPDATE {$CONFIG->dbprefix}users_entity set admin='no' where guid=$user_guid"); - invalidate_cache_for_entity($user_guid); + _elgg_invalidate_cache_for_entity($user_guid); return $r; } @@ -289,7 +290,7 @@ function remove_user_admin($user_guid) { * @param int $limit Number of results to return * @param int $offset Any indexing offset * - * @return false|array On success, an array of ElggSites + * @return ElggSite[]|false On success, an array of ElggSites */ function get_user_sites($user_guid, $limit = 10, $offset = 0) { $user_guid = (int)$user_guid; @@ -301,7 +302,7 @@ function get_user_sites($user_guid, $limit = 10, $offset = 0) { 'relationship' => 'member_of_site', 'relationship_guid' => $user_guid, 'inverse_relationship' => FALSE, - 'types' => 'site', + 'type' => 'site', 'limit' => $limit, 'offset' => $offset, )); @@ -342,8 +343,6 @@ function user_add_friend($user_guid, $friend_guid) { * @return bool Depending on success */ function user_remove_friend($user_guid, $friend_guid) { - global $CONFIG; - $user_guid = (int) $user_guid; $friend_guid = (int) $friend_guid; @@ -378,7 +377,7 @@ function user_is_friend($user_guid, $friend_guid) { * @param int $limit Number of results to return (default 10) * @param int $offset Indexing offset, if any * - * @return false|array Either an array of ElggUsers or false, depending on success + * @return ElggUser[]|false Either an array of ElggUsers or false, depending on success */ function get_user_friends($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0) { @@ -386,8 +385,8 @@ $offset = 0) { return elgg_get_entities_from_relationship(array( 'relationship' => 'friend', 'relationship_guid' => $user_guid, - 'types' => 'user', - 'subtypes' => $subtype, + 'type' => 'user', + 'subtype' => $subtype, 'limit' => $limit, 'offset' => $offset )); @@ -401,7 +400,7 @@ $offset = 0) { * @param int $limit Number of results to return (default 10) * @param int $offset Indexing offset, if any * - * @return false|array Either an array of ElggUsers or false, depending on success + * @return ElggUser[]|false Either an array of ElggUsers or false, depending on success */ function get_user_friends_of($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0) { @@ -410,8 +409,8 @@ $offset = 0) { 'relationship' => 'friend', 'relationship_guid' => $user_guid, 'inverse_relationship' => TRUE, - 'types' => 'user', - 'subtypes' => $subtype, + 'type' => 'user', + 'subtype' => $subtype, 'limit' => $limit, 'offset' => $offset )); @@ -427,7 +426,7 @@ $offset = 0) { * @param int $timelower The earliest time the entity can have been created. Default: all * @param int $timeupper The latest time the entity can have been created. Default: all * - * @return false|array An array of ElggObjects or false, depending on success + * @return ElggObject[]|false An array of ElggObjects or false, depending on success */ function get_user_friends_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0, $timelower = 0, $timeupper = 0) { @@ -554,13 +553,18 @@ function get_user($guid) { function get_user_by_username($username) { global $CONFIG, $USERNAME_TO_GUID_MAP_CACHE; + // Fixes #6052. Username is frequently sniffed from the path info, which, + // unlike $_GET, is not URL decoded. If the username was not URL encoded, + // this is harmless. + $username = rawurldecode($username); + $username = sanitise_string($username); $access = get_access_sql_suffix('e'); // Caching if ((isset($USERNAME_TO_GUID_MAP_CACHE[$username])) - && (retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]))) { - return retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]); + && (_elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]))) { + return _elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]); } $query = "SELECT e.* from {$CONFIG->dbprefix}users_entity u @@ -593,9 +597,9 @@ function get_user_by_code($code) { // Caching if ((isset($CODE_TO_GUID_MAP_CACHE[$code])) - && (retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]))) { + && (_elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]))) { - return retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]); + return _elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]); } $query = "SELECT e.* from {$CONFIG->dbprefix}users_entity u @@ -674,25 +678,22 @@ function find_active_users($seconds = 600, $limit = 10, $offset = 0, $count = fa * @return bool */ function send_new_password_request($user_guid) { - global $CONFIG; - $user_guid = (int)$user_guid; $user = get_entity($user_guid); - if ($user) { + if ($user instanceof ElggUser) { // generate code $code = generate_random_cleartext_password(); $user->setPrivateSetting('passwd_conf_code', $code); - // generate link - $link = $CONFIG->site->url . "resetpassword?u=$user_guid&c=$code"; + $link = elgg_get_site_url() . "resetpassword?u=$user_guid&c=$code"; // generate email $email = elgg_echo('email:resetreq:body', array($user->name, $_SERVER['REMOTE_ADDR'], $link)); - return notify_user($user->guid, $CONFIG->site->guid, - elgg_echo('email:resetreq:subject'), $email, NULL, 'email'); + return notify_user($user->guid, elgg_get_site_entity()->guid, + elgg_echo('email:resetreq:subject'), $email, array(), 'email'); } return false; @@ -709,19 +710,18 @@ function send_new_password_request($user_guid) { * @return bool */ function force_user_password_reset($user_guid, $password) { - global $CONFIG; - $user = get_entity($user_guid); + if ($user instanceof ElggUser) { + $ia = elgg_set_ignore_access(); - if ($user) { - $salt = generate_random_cleartext_password(); // Reset the salt - $user->salt = $salt; + $user->salt = generate_random_cleartext_password(); + $hash = generate_user_password($user, $password); + $user->password = $hash; + $result = (bool)$user->save(); - $hash = generate_user_password($user, $password); + elgg_set_ignore_access($ia); - $query = "UPDATE {$CONFIG->dbprefix}users_entity - set password='$hash', salt='$salt' where guid=$user_guid"; - return update_data($query); + return $result; } return false; @@ -741,7 +741,7 @@ function execute_new_password_request($user_guid, $conf_code) { $user_guid = (int)$user_guid; $user = get_entity($user_guid); - if ($user) { + if ($user instanceof ElggUser) { $saved_code = $user->getPrivateSetting('passwd_conf_code'); if ($saved_code && $saved_code == $conf_code) { @@ -755,7 +755,7 @@ function execute_new_password_request($user_guid, $conf_code) { $email = elgg_echo('email:resetpassword:body', array($user->name, $password)); return notify_user($user->guid, $CONFIG->site->guid, - elgg_echo('email:resetpassword:subject'), $email, NULL, 'email'); + elgg_echo('email:resetpassword:subject'), $email, array(), 'email'); } } } @@ -840,7 +840,7 @@ function validate_username($username) { for ($n = 0; $n < strlen($blacklist2); $n++) { if (strpos($username, $blacklist2[$n]) !== false) { $msg = elgg_echo('registration:invalidchars', array($blacklist2[$n], $blacklist2)); - $msg = htmlentities($msg, ENT_COMPAT, 'UTF-8'); + $msg = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8'); throw new RegistrationException($msg); } } @@ -907,13 +907,11 @@ function validate_email_address($address) { * @param string $invitecode An invite code from a friend * * @return int|false The new user's GUID; false on failure + * @throws RegistrationException */ function register_user($username, $password, $name, $email, $allow_multiple_emails = false, $friend_guid = 0, $invitecode = '') { - // Load the configuration - global $CONFIG; - // no need to trim password. $username = trim($username); $name = trim(strip_tags($name)); @@ -1030,7 +1028,7 @@ function elgg_get_user_validation_status($user_guid) { 'metadata_name' => 'validated' )); if ($md == false) { - return; + return null; } if ($md[0]->value) { @@ -1066,10 +1064,10 @@ function collections_submenu_items() { * @return bool * @access private */ -function friends_page_handler($page_elements, $handler) { +function friends_page_handler($segments, $handler) { elgg_set_context('friends'); - if (isset($page_elements[0]) && $user = get_user_by_username($page_elements[0])) { + if (isset($segments[0]) && $user = get_user_by_username($segments[0])) { elgg_set_page_owner_guid($user->getGUID()); } if (elgg_get_logged_in_user_guid() == elgg_get_page_owner_guid()) { @@ -1098,6 +1096,7 @@ function friends_page_handler($page_elements, $handler) { * @access private */ function collections_page_handler($page_elements) { + gatekeeper(); elgg_set_context('friends'); $base = elgg_get_config('path'); if (isset($page_elements[0])) { @@ -1196,13 +1195,11 @@ function set_last_login($user_guid) { * @param string $object_type user * @param ElggUser $object User object * - * @return bool + * @return void * @access private */ function user_create_hook_add_site_relationship($event, $object_type, $object) { - global $CONFIG; - - add_entity_relationship($object->getGUID(), 'member_of_site', $CONFIG->site->getGUID()); + add_entity_relationship($object->getGUID(), 'member_of_site', elgg_get_site_entity()->guid); } /** @@ -1232,6 +1229,7 @@ function user_avatar_hook($hook, $entity_type, $returnvalue, $params) { */ function elgg_user_hover_menu($hook, $type, $return, $params) { $user = $params['entity']; + /* @var ElggUser $user */ if (elgg_is_logged_in()) { if (elgg_get_logged_in_user_guid() != $user->guid) { @@ -1308,7 +1306,12 @@ function elgg_user_hover_menu($hook, $type, $return, $params) { /** * Setup the menu shown with an entity * + * @param string $hook + * @param string $type + * @param array $return + * @param array $params * @return array + * * @access private */ function elgg_users_setup_entity_menu($hook, $type, $return, $params) { @@ -1320,6 +1323,7 @@ function elgg_users_setup_entity_menu($hook, $type, $return, $params) { if (!elgg_instanceof($entity, 'user')) { return $return; } + /* @var ElggUser $entity */ if ($entity->isBanned()) { $banned = elgg_echo('banned'); @@ -1333,9 +1337,10 @@ function elgg_users_setup_entity_menu($hook, $type, $return, $params) { } else { $return = array(); if (isset($entity->location)) { + $location = htmlspecialchars($entity->location, ENT_QUOTES, 'UTF-8', false); $options = array( 'name' => 'location', - 'text' => "<span>$entity->location</span>", + 'text' => "<span>$location</span>", 'href' => false, 'priority' => 150, ); @@ -1586,7 +1591,7 @@ function users_init() { /** * Runs unit tests for ElggObject * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params diff --git a/engine/lib/views.php b/engine/lib/views.php index b92d9c57a..1142461fe 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; } @@ -218,7 +218,7 @@ function elgg_register_ajax_view($view) { /** * Unregister a view for ajax calls - * + * * @param string $view The view name * @return void * @since 1.8.3 @@ -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; } @@ -371,14 +369,14 @@ function elgg_view_exists($view, $viewtype = '', $recurse = true) { * view, $view_name plugin hook. * * @warning Any variables in $_SESSION will override passed vars - * upon name collision. See {@trac #2124}. + * upon name collision. See https://github.com/Elgg/Elgg/issues/2124 * * @param string $view The name and location of the view to use * @param array $vars Variables to pass to the view. * @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; } @@ -815,7 +795,7 @@ function elgg_view_menu($menu_name, array $vars = array()) { * - bool 'full_view' Whether to show a full or condensed view. * * @tip This function can automatically appends annotations to entities if in full - * view and a handler is registered for the entity:annotate. See {@trac 964} and + * view and a handler is registered for the entity:annotate. See https://github.com/Elgg/Elgg/issues/964 and * {@link elgg_view_entity_annotations()}. * * @param ElggEntity $entity The entity to display @@ -1012,6 +992,11 @@ function elgg_view_annotation(ElggAnnotation $annotation, array $vars = array(), function elgg_view_entity_list($entities, $vars = array(), $offset = 0, $limit = 10, $full_view = true, $list_type_toggle = true, $pagination = true) { + if (!$vars["limit"] && !$vars["offset"]) { + // no need for pagination if listing is unlimited
+ $vars["pagination"] = false;
+ }
+ if (!is_int($offset)) { $offset = (int)get_input('offset', 0); } @@ -1084,8 +1069,13 @@ function elgg_view_annotation_list($annotations, array $vars = array()) { 'full_view' => true, 'offset_key' => 'annoff', ); - + $vars = array_merge($defaults, $vars); + + if (!$vars["limit"] && !$vars["offset"]) {
+ // no need for pagination if listing is unlimited
+ $vars["pagination"] = false;
+ } return elgg_view('page/components/list', $vars); } @@ -1105,10 +1095,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 +1117,7 @@ function elgg_view_entity_annotations(ElggEntity $entity, $full_view = true) { * This is a shortcut for {@elgg_view page/elements/title}. * * @param string $title The page title - * @param 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 +1189,7 @@ function elgg_view_comments($entity, $add_comment = true, array $vars = array()) * * @param string $image The icon and other information * @param string $body Description content - * @param string $vars Additional parameters for the view + * @param array $vars Additional parameters for the view * * @return string * @since 1.8.0 @@ -1230,7 +1216,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 +1228,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 ''; } @@ -1258,6 +1247,17 @@ function elgg_view_river_item($item, array $vars = array()) { return ''; } + // @todo this needs to be cleaned up + // Don't hide objects in closed groups that a user can see. + // see https://github.com/elgg/elgg/issues/4789 + // else { + // // hide based on object's container + // $visibility = ElggGroupItemVisibility::factory($object->container_guid); + // if ($visibility->shouldHideItems) { + // return ''; + // } + // } + $vars['item'] = $item; return elgg_view('river/item', $vars); @@ -1320,7 +1320,7 @@ function elgg_view_form($action, $form_vars = array(), $body_vars = array()) { /** * View an item in a list * - * @param object $item ElggEntity or ElggAnnotation + * @param ElggEntity|ElggAnnotation $item * @param array $vars Additional parameters for the rendering * * @return string @@ -1339,22 +1339,22 @@ function elgg_view_list_item($item, array $vars = array()) { return elgg_view_river_item($item, $vars); } - return false; + return ''; } /** * View one of the elgg sprite icons - * + * * Shorthand for <span class="elgg-icon elgg-icon-$name"></span> - * + * * @param string $name The specific icon to display * @param string $class Additional class: float, float-alt, or custom class - * + * * @return string The html for displaying an icon */ function elgg_view_icon($name, $class = '') { // @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 +1403,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; } @@ -1462,17 +1463,13 @@ function elgg_get_views($dir, $base) { */ function elgg_view_tree($view_root, $viewtype = "") { global $CONFIG; - static $treecache; + static $treecache = array(); // Get viewtype if (!$viewtype) { $viewtype = elgg_get_viewtype(); } - // Has the treecache been initialised? - if (!isset($treecache)) { - $treecache = array(); - } // A little light internal caching if (!empty($treecache[$view_root])) { return $treecache[$view_root]; @@ -1516,17 +1513,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 +1601,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 +1618,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 +1642,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 (!$CONFIG->icon_sizes) { + if (!isset($CONFIG->icon_sizes)) { $icon_sizes = array( 'topbar' => array('w' => 16, 'h' => 16, 'square' => TRUE, 'upscale' => TRUE), 'tiny' => array('w' => 25, 'h' => 25, 'square' => TRUE, 'upscale' => TRUE), diff --git a/engine/lib/web_services.php b/engine/lib/web_services.php index da3ed76a9..51cad6f39 100644 --- a/engine/lib/web_services.php +++ b/engine/lib/web_services.php @@ -178,7 +178,7 @@ function authenticate_method($method) { // check if user authentication is required if ($API_METHODS[$method]["require_user_auth"] == true) { if ($user_auth_result == false) { - throw new APIException($user_pam->getFailureMessage()); + throw new APIException($user_pam->getFailureMessage(), ErrorResult::$RESULT_FAIL_AUTHTOKEN); } } @@ -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 @@ -1165,6 +1166,17 @@ function list_all_apis() { * @access private */ function auth_gettoken($username, $password) { + // check if username is an email address
+ if (is_email_address($username)) {
+ $users = get_user_by_email($username);
+
+ // check if we have a unique user
+ if (is_array($users) && (count($users) == 1)) {
+ $username = $users[0]->username;
+ }
+ }
+
+ // validate username and password if (true === elgg_authenticate($username, $password)) { $token = create_user_token($username); if ($token) { @@ -1194,6 +1206,8 @@ $ERRORS = array(); * * @return void * @access private + * + * @throws Exception */ function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { global $ERRORS; @@ -1264,25 +1278,23 @@ function service_handler($handler, $request) { $request = explode('/', $request); // after the handler, the first identifier is response format - // ex) http://example.org/services/api/rest/xml/?method=test - $reponse_format = array_shift($request); + // ex) http://example.org/services/api/rest/json/?method=test + $response_format = array_shift($request); // Which view - xml, json, ... - if ($reponse_format) { - elgg_set_viewtype($reponse_format); + if ($response_format && elgg_is_valid_view_type($response_format)) { + elgg_set_viewtype($response_format); } else { - // default to xml - elgg_set_viewtype("xml"); + // default to json + elgg_set_viewtype("json"); } if (!isset($CONFIG->servicehandler) || empty($handler)) { // 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 +1313,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 +1332,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 +1348,8 @@ function unregister_service_handler($handler) { * * @return void * @access private + * + * @throws SecurityException|APIException */ function rest_handler() { global $CONFIG; @@ -1387,7 +1404,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 +1414,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 +1436,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/widgets.php b/engine/lib/widgets.php index d73dd6330..699462a1b 100644 --- a/engine/lib/widgets.php +++ b/engine/lib/widgets.php @@ -336,7 +336,7 @@ function elgg_default_widgets_init() { * * @param string $event The event * @param string $type The type of object - * @param object $entity The entity being created + * @param ElggEntity $entity The entity being created * @return void * @access private */ @@ -372,6 +372,7 @@ function elgg_create_default_widgets($event, $type, $entity) { ); $widgets = elgg_get_entities_from_private_settings($options); + /* @var ElggWidget[] $widgets */ foreach ($widgets as $widget) { // change the container and owner diff --git a/engine/lib/xml.php b/engine/lib/xml.php index 813bc4ee0..497459d83 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 + * @return ElggXMLElement */ 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); } |
