From e7e0ab9d0b33121e38a236879fcca417072140f6 Mon Sep 17 00:00:00 2001 From: Josh Woody Date: Mon, 17 May 2010 12:12:00 -0500 Subject: [PATCH 0001/1142] [feature/prune-users] Rework user_delete() functions_user.php user_delete now uses fewer queries to delete a set of users of size > 1 by accepting an array of users rather than a single user at a time. This required changing the third parameter, however the function retains its former behavior with the old-style parameters. PHPBB3-9622 --- phpBB/includes/acp/acp_inactive.php | 5 +- phpBB/includes/acp/acp_prune.php | 5 +- phpBB/includes/functions_user.php | 279 +++++++++++++++------------- 3 files changed, 156 insertions(+), 133 deletions(-) diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index e4fb695a11..833727084f 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -157,10 +157,7 @@ class acp_inactive trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); } - foreach ($mark as $user_id) - { - user_delete('retain', $user_id, $user_affected[$user_id]); - } + user_delete('retain', $mark, true); add_log('admin', 'LOG_INACTIVE_' . strtoupper($action), implode(', ', $user_affected)); } diff --git a/phpBB/includes/acp/acp_prune.php b/phpBB/includes/acp/acp_prune.php index 6120c15d68..99bec29d6d 100644 --- a/phpBB/includes/acp/acp_prune.php +++ b/phpBB/includes/acp/acp_prune.php @@ -265,10 +265,7 @@ class acp_prune } else { - foreach ($user_ids as $user_id) - { - user_delete('retain', $user_id, $usernames[$user_id]); - } + user_delete('retain', $user_ids, true); $l_log = 'LOG_PRUNE_USER_DEL_ANON'; } diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 317578cd54..0886e8aabf 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -330,20 +330,32 @@ function user_add($user_row, $cp_data = false) /** * Remove User +* @param $mode Either 'retain' or 'remove' */ -function user_delete($mode, $user_id, $post_username = false) +function user_delete($mode, $user_ids, $retain_username = true) { global $cache, $config, $db, $user, $auth; global $phpbb_root_path, $phpEx; + $db->sql_transaction('begin'); + + $user_rows = array(); + if (!is_array($user_ids)) + { + $user_ids = array($user_ids); + } + $sql = 'SELECT * FROM ' . USERS_TABLE . ' - WHERE user_id = ' . $user_id; + WHERE ' . $db->sql_in_set('user_id', $user_ids); $result = $db->sql_query($sql); - $user_row = $db->sql_fetchrow($result); + while ($row = $db->sql_fetchrow($result)) + { + $user_rows[$row['user_id']] = $row; + } $db->sql_freeresult($result); - if (!$user_row) + if (!$user_rows) { return false; } @@ -351,7 +363,8 @@ function user_delete($mode, $user_id, $post_username = false) // Before we begin, we will remove the reports the user issued. $sql = 'SELECT r.post_id, p.topic_id FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . ' p - WHERE r.user_id = ' . $user_id . ' + WHERE ' . $db->sql_in_set('r.user_id', $user_ids) . //r.user_id = ' . $user_id . ' + ' AND p.post_id = r.post_id'; $result = $db->sql_query($sql); @@ -405,135 +418,157 @@ function user_delete($mode, $user_id, $post_username = false) } // Remove reports - $db->sql_query('DELETE FROM ' . REPORTS_TABLE . ' WHERE user_id = ' . $user_id); + $db->sql_query('DELETE FROM ' . REPORTS_TABLE . ' WHERE ' . $db->sql_in_set('user_id', $user_ids)); - if ($user_row['user_avatar'] && $user_row['user_avatar_type'] == AVATAR_UPLOAD) + // Some things need to be done in the loop (if the query changes based + // on which user is currently being deleted) + $added_guest_posts = 0; + foreach ($user_rows as $user_id => $user_row) { - avatar_delete('user', $user_row); + if ($user_row['user_avatar'] && $user_row['user_avatar_type'] == AVATAR_UPLOAD) + { + avatar_delete('user', $user_row); + } + + // Decrement number of users if this user is active + if ($user_row['user_type'] != USER_INACTIVE && $user_row['user_type'] != USER_IGNORE) + { + set_config_count('num_users', -1, true); + } + + switch ($mode) + { + case 'retain': + if ($retain_username === false) + { + $post_username = $user->lang['GUEST']; + } + else + { + $post_username = $user_row['username']; + } + + // If the user is inactive and newly registered we assume no posts from the user, and save the queries + if ($user_row['user_type'] == USER_INACTIVE && $user_row['user_inactive_reason'] == INACTIVE_REGISTER && !$user_row['user_posts']) + { + } + else + { + // When we delete these users and retain the posts, we must assign all the data to the guest user + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET forum_last_poster_id = ' . ANONYMOUS . ", forum_last_poster_name = '" . $db->sql_escape($post_username) . "', forum_last_poster_colour = '' + WHERE forum_last_poster_id = $user_id"; + $db->sql_query($sql); + + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET poster_id = ' . ANONYMOUS . ", post_username = '" . $db->sql_escape($post_username) . "' + WHERE poster_id = $user_id"; + $db->sql_query($sql); + + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_poster = ' . ANONYMOUS . ", topic_first_poster_name = '" . $db->sql_escape($post_username) . "', topic_first_poster_colour = '' + WHERE topic_poster = $user_id"; + $db->sql_query($sql); + + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_last_poster_id = ' . ANONYMOUS . ", topic_last_poster_name = '" . $db->sql_escape($post_username) . "', topic_last_poster_colour = '' + WHERE topic_last_poster_id = $user_id"; + $db->sql_query($sql); + + // Since we change every post by this author, we need to count this amount towards the anonymous user + + if ($user_row['user_posts']) + { + $added_guest_posts += $user_row['user_posts']; + } + } + break; + + case 'remove': + // there is nothing variant specific to deleting posts + break; + } } - switch ($mode) + // Now do the invariant tasks + // all queries performed in one call of this function are in a single transaction + // so this is kosher + if ($mode == 'retain') { - case 'retain': + // Assign more data to the Anonymous user + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET poster_id = ' . ANONYMOUS . ' + WHERE ' . $db->sql_in_set('poster_id', $user_ids); + $db->sql_query($sql); - $db->sql_transaction('begin'); + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_edit_user = ' . ANONYMOUS . ' + WHERE ' . $db->sql_in_set('post_edit_user', $user_ids); + $db->sql_query($sql); - if ($post_username === false) - { - $post_username = $user->lang['GUEST']; - } + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = user_posts + ' . $added_guest_posts . ' + WHERE user_id = ' . ANONYMOUS; + $db->sql_query($sql); + } + else if ($mode == 'remove') + { + if (!function_exists('delete_posts')) + { + include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + } - // If the user is inactive and newly registered we assume no posts from this user being there... - if ($user_row['user_type'] == USER_INACTIVE && $user_row['user_inactive_reason'] == INACTIVE_REGISTER && !$user_row['user_posts']) - { - } - else - { - $sql = 'UPDATE ' . FORUMS_TABLE . ' - SET forum_last_poster_id = ' . ANONYMOUS . ", forum_last_poster_name = '" . $db->sql_escape($post_username) . "', forum_last_poster_colour = '' - WHERE forum_last_poster_id = $user_id"; - $db->sql_query($sql); + // Find any topics whose only posts are being deleted, and remove them from the topics table + $sql = 'SELECT topic_id, COUNT(post_id) AS total_posts + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('poster_id', $user_ids) . ' + GROUP BY topic_id'; + $result = $db->sql_query($sql); - $sql = 'UPDATE ' . POSTS_TABLE . ' - SET poster_id = ' . ANONYMOUS . ", post_username = '" . $db->sql_escape($post_username) . "' - WHERE poster_id = $user_id"; - $db->sql_query($sql); + $topic_id_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $topic_id_ary[$row['topic_id']] = $row['total_posts']; + } + $db->sql_freeresult($result); - $sql = 'UPDATE ' . POSTS_TABLE . ' - SET post_edit_user = ' . ANONYMOUS . " - WHERE post_edit_user = $user_id"; - $db->sql_query($sql); - - $sql = 'UPDATE ' . TOPICS_TABLE . ' - SET topic_poster = ' . ANONYMOUS . ", topic_first_poster_name = '" . $db->sql_escape($post_username) . "', topic_first_poster_colour = '' - WHERE topic_poster = $user_id"; - $db->sql_query($sql); - - $sql = 'UPDATE ' . TOPICS_TABLE . ' - SET topic_last_poster_id = ' . ANONYMOUS . ", topic_last_poster_name = '" . $db->sql_escape($post_username) . "', topic_last_poster_colour = '' - WHERE topic_last_poster_id = $user_id"; - $db->sql_query($sql); - - $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' - SET poster_id = ' . ANONYMOUS . " - WHERE poster_id = $user_id"; - $db->sql_query($sql); - - // Since we change every post by this author, we need to count this amount towards the anonymous user - - // Update the post count for the anonymous user - if ($user_row['user_posts']) - { - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_posts = user_posts + ' . $user_row['user_posts'] . ' - WHERE user_id = ' . ANONYMOUS; - $db->sql_query($sql); - } - } - - $db->sql_transaction('commit'); - - break; - - case 'remove': - - if (!function_exists('delete_posts')) - { - include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); - } - - $sql = 'SELECT topic_id, COUNT(post_id) AS total_posts - FROM ' . POSTS_TABLE . " - WHERE poster_id = $user_id - GROUP BY topic_id"; + if (sizeof($topic_id_ary)) + { + $sql = 'SELECT topic_id, topic_replies, topic_replies_real + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', array_keys($topic_id_ary)); $result = $db->sql_query($sql); - $topic_id_ary = array(); + $del_topic_ary = array(); while ($row = $db->sql_fetchrow($result)) { - $topic_id_ary[$row['topic_id']] = $row['total_posts']; + if (max($row['topic_replies'], $row['topic_replies_real']) + 1 == $topic_id_ary[$row['topic_id']]) + { + $del_topic_ary[] = $row['topic_id']; + } } $db->sql_freeresult($result); - if (sizeof($topic_id_ary)) + if (sizeof($del_topic_ary)) { - $sql = 'SELECT topic_id, topic_replies, topic_replies_real - FROM ' . TOPICS_TABLE . ' - WHERE ' . $db->sql_in_set('topic_id', array_keys($topic_id_ary)); - $result = $db->sql_query($sql); - - $del_topic_ary = array(); - while ($row = $db->sql_fetchrow($result)) - { - if (max($row['topic_replies'], $row['topic_replies_real']) + 1 == $topic_id_ary[$row['topic_id']]) - { - $del_topic_ary[] = $row['topic_id']; - } - } - $db->sql_freeresult($result); - - if (sizeof($del_topic_ary)) - { - $sql = 'DELETE FROM ' . TOPICS_TABLE . ' - WHERE ' . $db->sql_in_set('topic_id', $del_topic_ary); - $db->sql_query($sql); - } + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $del_topic_ary); + $db->sql_query($sql); } + } - // Delete posts, attachments, etc. - delete_posts('poster_id', $user_id); - - break; + // actually do the meat of the work if we are deleting all the users' posts + // delete_posts can handle any number of IDs in its second argument + delete_posts('poster_id', $user_ids); } - $db->sql_transaction('begin'); - $table_ary = array(USERS_TABLE, USER_GROUP_TABLE, TOPICS_WATCH_TABLE, FORUMS_WATCH_TABLE, ACL_USERS_TABLE, TOPICS_TRACK_TABLE, TOPICS_POSTED_TABLE, FORUMS_TRACK_TABLE, PROFILE_FIELDS_DATA_TABLE, MODERATOR_CACHE_TABLE, DRAFTS_TABLE, BOOKMARKS_TABLE, SESSIONS_KEYS_TABLE, PRIVMSGS_FOLDER_TABLE, PRIVMSGS_RULES_TABLE); + // Delete the miscellaneous (non-post) data for the user foreach ($table_ary as $table) { $sql = "DELETE FROM $table - WHERE user_id = $user_id"; + WHERE " . $db->sql_in_set('user_id', $user_ids); $db->sql_query($sql); } @@ -541,35 +576,35 @@ function user_delete($mode, $user_id, $post_username = false) // Delete user log entries about this user $sql = 'DELETE FROM ' . LOG_TABLE . ' - WHERE reportee_id = ' . $user_id; + WHERE ' . $db->sql_in_set('user_id', $user_ids); //reportee_id = ' . $user_id; $db->sql_query($sql); // Change user_id to anonymous for this users triggered events $sql = 'UPDATE ' . LOG_TABLE . ' SET user_id = ' . ANONYMOUS . ' - WHERE user_id = ' . $user_id; + WHERE ' . $db->sql_in_set('user_id', $user_ids); //user_id = ' . $user_id; $db->sql_query($sql); // Delete the user_id from the zebra table $sql = 'DELETE FROM ' . ZEBRA_TABLE . ' - WHERE user_id = ' . $user_id . ' - OR zebra_id = ' . $user_id; + WHERE ' . $db->sql_in_set('user_id', $user_ids) . //user_id = ' . $user_id . ' + ' OR ' . $db->sql_in_set('zebra_id', $user_ids); $db->sql_query($sql); // Delete the user_id from the banlist $sql = 'DELETE FROM ' . BANLIST_TABLE . ' - WHERE ban_userid = ' . $user_id; + WHERE ' . $db->sql_in_set('ban_userid', $user_ids); $db->sql_query($sql); // Delete the user_id from the session table $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' - WHERE session_user_id = ' . $user_id; + WHERE ' . $db->sql_in_set('session_user_id', $user_ids); $db->sql_query($sql); // Remove any undelivered mails... $sql = 'SELECT msg_id, user_id FROM ' . PRIVMSGS_TO_TABLE . ' - WHERE author_id = ' . $user_id . ' + WHERE ' . $db->sql_in_set('author_id', $user_ids) . ' AND folder_id = ' . PRIVMSGS_NO_BOX; $result = $db->sql_query($sql); @@ -589,24 +624,24 @@ function user_delete($mode, $user_id, $post_username = false) } $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' - WHERE author_id = ' . $user_id . ' + WHERE ' . $db->sql_in_set('author_id', $user_id) . ' AND folder_id = ' . PRIVMSGS_NO_BOX; $db->sql_query($sql); // Delete all to-information $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' - WHERE user_id = ' . $user_id; + WHERE ' . $db->sql_in_set('user_id', $user_ids); $db->sql_query($sql); // Set the remaining author id to anonymous - this way users are still able to read messages from users being removed $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' SET author_id = ' . ANONYMOUS . ' - WHERE author_id = ' . $user_id; + WHERE ' . $db->sql_in_set('author_id', $user_ids); $db->sql_query($sql); $sql = 'UPDATE ' . PRIVMSGS_TABLE . ' SET author_id = ' . ANONYMOUS . ' - WHERE author_id = ' . $user_id; + WHERE ' . $db->sql_in_set('author_id', $user_id); $db->sql_query($sql); foreach ($undelivered_user as $_user_id => $ary) @@ -626,17 +661,11 @@ function user_delete($mode, $user_id, $post_username = false) $db->sql_transaction('commit'); // Reset newest user info if appropriate - if ($config['newest_user_id'] == $user_id) + if (in_array($config['newest_user_id'], $user_ids)) { update_last_username(); } - // Decrement number of users if this user is active - if ($user_row['user_type'] != USER_INACTIVE && $user_row['user_type'] != USER_IGNORE) - { - set_config_count('num_users', -1, true); - } - return false; } From 04a6303527d9bf29114b51001d06af5235ea0847 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 8 May 2011 03:01:38 -0400 Subject: [PATCH 0002/1142] [feature/prune-users] Apply e6ed55a9c1ceb07ab2e87d4a53f9e688fda319c5. This was done in PHPBB3-9872. PHPBB3-9622 --- phpBB/includes/functions_user.php | 41 +------------------------------ 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 0886e8aabf..9b16a56b1c 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -518,46 +518,7 @@ function user_delete($mode, $user_ids, $retain_username = true) include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); } - // Find any topics whose only posts are being deleted, and remove them from the topics table - $sql = 'SELECT topic_id, COUNT(post_id) AS total_posts - FROM ' . POSTS_TABLE . ' - WHERE ' . $db->sql_in_set('poster_id', $user_ids) . ' - GROUP BY topic_id'; - $result = $db->sql_query($sql); - - $topic_id_ary = array(); - while ($row = $db->sql_fetchrow($result)) - { - $topic_id_ary[$row['topic_id']] = $row['total_posts']; - } - $db->sql_freeresult($result); - - if (sizeof($topic_id_ary)) - { - $sql = 'SELECT topic_id, topic_replies, topic_replies_real - FROM ' . TOPICS_TABLE . ' - WHERE ' . $db->sql_in_set('topic_id', array_keys($topic_id_ary)); - $result = $db->sql_query($sql); - - $del_topic_ary = array(); - while ($row = $db->sql_fetchrow($result)) - { - if (max($row['topic_replies'], $row['topic_replies_real']) + 1 == $topic_id_ary[$row['topic_id']]) - { - $del_topic_ary[] = $row['topic_id']; - } - } - $db->sql_freeresult($result); - - if (sizeof($del_topic_ary)) - { - $sql = 'DELETE FROM ' . TOPICS_TABLE . ' - WHERE ' . $db->sql_in_set('topic_id', $del_topic_ary); - $db->sql_query($sql); - } - } - - // actually do the meat of the work if we are deleting all the users' posts + // Delete posts, attachments, etc. // delete_posts can handle any number of IDs in its second argument delete_posts('poster_id', $user_ids); } From fe347ec1124de7022bbbf37d0a1fffeff56c7271 Mon Sep 17 00:00:00 2001 From: Josh Woody Date: Tue, 25 May 2010 10:52:01 -0500 Subject: [PATCH 0003/1142] [feature/prune-users] Adjust some language strings for new features Adjust two language strings for ACP prune feature to include descriptions for new features. PHPBB3-9622 --- phpBB/adm/style/acp_prune_users.html | 27 ++++- phpBB/adm/style/confirm_body_prune.html | 28 +++-- phpBB/includes/acp/acp_prune.php | 139 +++++++++++++++++++----- phpBB/language/en/acp/prune.php | 14 ++- 4 files changed, 162 insertions(+), 46 deletions(-) diff --git a/phpBB/adm/style/acp_prune_users.html b/phpBB/adm/style/acp_prune_users.html index 0f2b23dcef..a6c5679a11 100644 --- a/phpBB/adm/style/acp_prune_users.html +++ b/phpBB/adm/style/acp_prune_users.html @@ -9,7 +9,7 @@
- {L_ACP_PRUNE_USERS} + {L_CRITERIA}
@@ -18,9 +18,16 @@
+
+
+
+

{L_JOINED_EXPLAIN}
-
+
+ {L_AFTER} +

{L_BEFORE} +

{L_LAST_ACTIVE_EXPLAIN}
@@ -30,11 +37,27 @@
+
+
+
+
+
+

{L_PRUNE_USERS_GROUP_EXPLAIN}
+
+
+
+ +
+ {L_USERNAMES}

{L_SELECT_USERS_EXPLAIN}
[ {L_FIND_USERNAME} ]
+
+ +
+ {L_OPTIONS}

{L_DELETE_USER_POSTS_EXPLAIN}
diff --git a/phpBB/adm/style/confirm_body_prune.html b/phpBB/adm/style/confirm_body_prune.html index 9481386231..92e0a22e71 100644 --- a/phpBB/adm/style/confirm_body_prune.html +++ b/phpBB/adm/style/confirm_body_prune.html @@ -2,6 +2,23 @@ +
+

{L_PRUNE_USERS_LIST}

+

{L_PRUNE_USERS_LIST_DEACTIVATE}

{L_PRUNE_USERS_LIST_DELETE}

+ +
+ + » + {users.USERNAME} + [{L_USER_ADMIN}]
+ +
+ + {L_MARK_ALL} • + {L_UNMARK_ALL} + +
+

{MESSAGE_TITLE}

{MESSAGE_TEXT}

@@ -12,17 +29,6 @@   - -

{L_PRUNE_USERS_LIST}

-

{L_PRUNE_USERS_LIST_DEACTIVATE}

{L_PRUNE_USERS_LIST_DELETE}

- -
- - » {users.USERNAME} [{L_USER_ADMIN}]
- - -

-
diff --git a/phpBB/includes/acp/acp_prune.php b/phpBB/includes/acp/acp_prune.php index 99bec29d6d..a21805972f 100644 --- a/phpBB/includes/acp/acp_prune.php +++ b/phpBB/includes/acp/acp_prune.php @@ -243,6 +243,7 @@ class acp_prune if (confirm_box(true)) { $user_ids = $usernames = array(); + $this->get_prune_users($user_ids, $usernames); if (sizeof($user_ids)) @@ -256,10 +257,7 @@ class acp_prune { if ($deleteposts) { - foreach ($user_ids as $user_id) - { - user_delete('remove', $user_id); - } + user_delete('remove', $user_ids); $l_log = 'LOG_PRUNE_USER_DEL_DEL'; } @@ -297,6 +295,7 @@ class acp_prune { $template->assign_block_vars('users', array( 'USERNAME' => $usernames[$user_id], + 'USER_ID' => $user_id, 'U_PROFILE' => append_sid($phpbb_root_path . 'memberlist.' . $phpEx, 'mode=viewprofile&u=' . $user_id), 'U_USER_ADMIN' => ($auth->acl_get('a_user')) ? append_sid("{$phpbb_admin_path}index.$phpEx", 'i=users&mode=overview&u=' . $user_id, true, $user->session_id) : '', )); @@ -312,8 +311,8 @@ class acp_prune 'mode' => $mode, 'prune' => 1, - 'users' => utf8_normalize_nfc(request_var('users', '', true)), - 'username' => utf8_normalize_nfc(request_var('username', '', true)), + 'users' => request_var('users', '', true), + 'username' => request_var('username', '', true), 'email' => request_var('email', ''), 'joined_select' => request_var('joined_select', ''), 'joined' => request_var('joined', ''), @@ -338,22 +337,27 @@ class acp_prune } $find_time = array('lt' => $user->lang['BEFORE'], 'gt' => $user->lang['AFTER']); - $s_find_join_time = ''; - foreach ($find_time as $key => $value) - { - $s_find_join_time .= ''; - } - $s_find_active_time = ''; foreach ($find_time as $key => $value) { $s_find_active_time .= ''; } + $s_group_list = ''; + $sql = 'SELECT group_id, group_name FROM ' . GROUPS_TABLE . ' + WHERE group_type <> ' . GROUP_SPECIAL . ' + ORDER BY group_name ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $s_group_list .= '
+

{L_PRUNE_USERS_GROUP_EXPLAIN}
+
diff --git a/phpBB/includes/acp/acp_prune.php b/phpBB/includes/acp/acp_prune.php index a21805972f..b10eb292d2 100644 --- a/phpBB/includes/acp/acp_prune.php +++ b/phpBB/includes/acp/acp_prune.php @@ -245,7 +245,6 @@ class acp_prune $user_ids = $usernames = array(); $this->get_prune_users($user_ids, $usernames); - if (sizeof($user_ids)) { if ($action == 'deactivate') @@ -311,17 +310,7 @@ class acp_prune 'mode' => $mode, 'prune' => 1, - 'users' => request_var('users', '', true), - 'username' => request_var('username', '', true), - 'email' => request_var('email', ''), - 'joined_select' => request_var('joined_select', ''), - 'joined' => request_var('joined', ''), - 'active_select' => request_var('active_select', ''), - 'active' => request_var('active', ''), - 'count_select' => request_var('count_select', ''), - 'count' => request_var('count', ''), 'deleteposts' => request_var('deleteposts', 0), - 'action' => request_var('action', ''), )), 'confirm_body_prune.html'); } @@ -343,7 +332,7 @@ class acp_prune $s_find_active_time .= ''; } - $s_group_list = ''; + $s_group_list = ''; $sql = 'SELECT group_id, group_name FROM ' . GROUPS_TABLE . ' WHERE group_type <> ' . GROUP_SPECIAL . ' ORDER BY group_name ASC'; @@ -372,6 +361,8 @@ class acp_prune $users_by_name = request_var('users', '', true); $users_by_id = request_var('user_ids', array(0)); + $group_id = request_var('group_id', 0); + $posts_on_queue = request_var('posts_on_queue', 0); if ($users_by_name) { @@ -399,7 +390,6 @@ class acp_prune $active = request_var('active', ''); $count = request_var('count', 0); - $posts_on_queue = request_var('posts_on_queue', 0); $active = ($active) ? explode('-', $active) : array(); $joined_before = ($joined_before) ? explode('-', $joined_before) : array(); @@ -424,7 +414,7 @@ class acp_prune } else if (empty($joined_after) && !empty($joined_before)) { - $joined_sql = ' AND user_regdate < ' . gmmktime(0, 0, 0, (int) $joined_before[1], (int) $joined_before[2], (int) $joined_before[3]); + $joined_sql = ' AND user_regdate < ' . gmmktime(0, 0, 0, (int) $joined_before[1], (int) $joined_before[2], (int) $joined_before[0]); } // implicit else when both arrays are empty do nothing From 6f7d095e3f4b09847c5963646b2f4a817d68ba39 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Sun, 18 Apr 2010 22:11:04 +0100 Subject: [PATCH 0012/1142] [feature/new-tz-handling] Wrapper around DateTime for new date time handling. Wrapped PHP's DateTime with some extensions for supporting phpBB's relative date formats and provided the DateTime::getTimestamp() method to PHP < 5.3. PHPBB3-9558 --- phpBB/includes/datetime.php | 160 ++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 phpBB/includes/datetime.php diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php new file mode 100644 index 0000000000..b1e95e58a3 --- /dev/null +++ b/phpBB/includes/datetime.php @@ -0,0 +1,160 @@ +_user = $user ? $user : $GLOBALS['user']; + + $timezone = (!$timezone && $this->_user->tz instanceof DateTimeZone) ? $this->_user->tz : $timezone; + + parent::__construct($time, $timezone); + } + + /** + * Returns a UNIX timestamp representation of the date time. + * + * @return int UNIX timestamp + */ + public function getTimestamp() + { + static $compat; + + if (!isset($compat)) + { + $compat = !method_exists('DateTime', 'getTimestamp'); + } + + return !$compat ? parent::getTimestamp() : (int) parent::format('U'); + } + + /** + * Formats the current date time into the specified format + * + * @param string $format Optional format to use for output, defaults to users chosen format + * @param boolean $force_absolute Force output of a non relative date + * @return string Formatted date time + */ + public function format($format = '', $force_absolute = false) + { + $format = $format ? $format : $this->_user->date_format; + $relative = (strpos($format, self::RELATIVE_WRAPPER) !== false && !$force_absolute); + $now = new self('now', $this->_user->tz, $this->_user); + $delta = $now->getTimestamp() - $this->getTimestamp(); + + if ($relative) + { + if ($delta <= 3600 && ($delta >= -5 || (($now->getTimestamp() / 60) % 60) == (($this->getTimestamp() / 60) % 60)) && isset($this->_user->lang['datetime']['AGO'])) + { + return $this->_user->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); + } + else + { + $midnight = clone $now; + $midnight->setTime(0, 0, 0); + + $midnight = $midnight->getTimestamp(); + $gmepoch = $this->getTimestamp(); + + if (!($gmepoch < $midnight - 86400 || $gmepoch > $midnight + 172800)) + { + $day = false; + + if ($gmepoch > $midnight + 86400) + { + $day = 'TOMORROW'; + } + else if ($gmepoch > $midnight) + { + $day = 'TODAY'; + } + else if ($gmepoch > $midnight - 86400) + { + $day = 'YESTERDAY'; + } + + if ($day !== false) + { + $format = self::_format_cache($format, $this->_user); + + return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->_user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang'])); + } + } + } + } + + $format = self::_format_cache($format, $this->_user); + + return strtr(parent::format($format['format_long']), $format['lang']); + } + + /** + * Pre-processes the specified date format + * + * @param string $format Output format + * @param user $user User object to use for localisation + * @return array Processed date format + */ + static protected function _format_cache($format, $user) + { + $lang = $user->lang_name; + + if (!isset(self::$format_cache[$lang])) + { + self::$format_cache[$lang] = array(); + + if (!isset(self::$format_cache[$lang][$format])) + { + // Is the user requesting a friendly date format (i.e. 'Today 12:42')? + self::$format_cache[$lang][$format] = array( + 'is_short' => strpos($format, self::RELATIVE_WRAPPER) !== false, + 'format_short' => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1), + 'format_long' => str_replace(self::RELATIVE_WRAPPER, '', $format), + 'lang' => $user->lang['datetime'], + ); + + // Short representation of month in format? Some languages use different terms for the long and short format of May + if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) + { + self::$format_cache[$lang][$format]['lang']['May'] = $user->lang['datetime']['May_short']; + } + } + } + + return self::$format_cache[$lang][$format]; + } +} From e8b60fc3d8b483fda50c715e59b73861cd3ced9e Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Sun, 18 Apr 2010 22:26:33 +0100 Subject: [PATCH 0013/1142] [feature/new-tz-handling] Use phpbb_datetime rather than phpbb_DateTime. PHPBB3-9558 --- phpBB/includes/datetime.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index b1e95e58a3..d14693faa3 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -11,7 +11,7 @@ * phpBB custom extensions to the PHP DateTime class * This handles the relative formats phpBB employs */ -class phpbb_DateTime extends DateTime +class phpbb_datetime extends DateTime { /** * String used to wrap the date segment which should be replaced by today/tomorrow/yesterday @@ -29,7 +29,7 @@ class phpbb_DateTime extends DateTime static protected $format_cache = array(); /** - * Constructs a new instance of phpbb_DateTime, expanded to include an argument to inject + * Constructs a new instance of phpbb_datetime, expanded to include an argument to inject * the user context and modify the timezone to the users selected timezone if one is not set. * * @param string $time String in a format accepted by strtotime(). From a5c3ff376911f2f25595f1a540c7a16395dac67d Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Tue, 20 Apr 2010 19:30:05 +0100 Subject: [PATCH 0014/1142] [feature/new-tz-handling] Renamed old variables and removed extra conditional. PHPBB3-9558 --- phpBB/includes/datetime.php | 39 ++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index d14693faa3..92aef88599 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -87,32 +87,27 @@ class phpbb_datetime extends DateTime $midnight = clone $now; $midnight->setTime(0, 0, 0); - $midnight = $midnight->getTimestamp(); - $gmepoch = $this->getTimestamp(); + $midnight = $midnight->getTimestamp(); + $timestamp = $this->getTimestamp(); - if (!($gmepoch < $midnight - 86400 || $gmepoch > $midnight + 172800)) + if ($timestamp > $midnight + 86400) { - $day = false; + $day = 'TOMORROW'; + } + else if ($timestamp > $midnight) + { + $day = 'TODAY'; + } + else if ($timestamp > $midnight - 86400) + { + $day = 'YESTERDAY'; + } - if ($gmepoch > $midnight + 86400) - { - $day = 'TOMORROW'; - } - else if ($gmepoch > $midnight) - { - $day = 'TODAY'; - } - else if ($gmepoch > $midnight - 86400) - { - $day = 'YESTERDAY'; - } + if ($day !== false) + { + $format = self::_format_cache($format, $this->_user); - if ($day !== false) - { - $format = self::_format_cache($format, $this->_user); - - return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->_user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang'])); - } + return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->_user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang'])); } } } From 3559d2062480d5c3a0e94dae7388f9f1e5918ea7 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Tue, 20 Apr 2010 19:43:49 +0100 Subject: [PATCH 0015/1142] [feature/new-tz-handling] Update user methods to use new date processing class. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit user::setup() now stores a DateTimeZone object in user::$timezone representing the users timezone. For backwards compatibility a numeric value in user/board_timezone will be converted into one of the legacy Etc/GMT±X timezones. This will be used until the user updates his/her timezone in the UCP. user::format_date() is now basically a legacy wrapper that transforms a UTC UNIX timestamp into a formatted localised date using phpbb_datetime::format(). PHPBB3-9558 --- phpBB/includes/session.php | 87 +++++++++++--------------------------- 1 file changed, 24 insertions(+), 63 deletions(-) diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index 497aaf1141..ba9136b568 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -1574,20 +1574,26 @@ class user extends session { global $db, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache; + // @todo Move this to a better location + if (!class_exists('phpbb_datetime')) + { + global $phpbb_root_path, $phpEx; + + require "{$phpbb_root_path}includes/datetime.$phpEx"; + } + if ($this->data['user_id'] != ANONYMOUS) { $this->lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); $this->date_format = $this->data['user_dateformat']; - $this->timezone = $this->data['user_timezone'] * 3600; - $this->dst = $this->data['user_dst'] * 3600; + $this->timezone = $this->data['user_timezone']; } else { $this->lang_name = basename($config['default_lang']); $this->date_format = $config['default_dateformat']; - $this->timezone = $config['board_timezone'] * 3600; - $this->dst = $config['board_dst'] * 3600; + $this->timezone = $config['board_timezone']; /** * If a guest user is surfing, we try to guess his/her language first by obtaining the browser language @@ -1626,6 +1632,14 @@ class user extends session */ } + if (is_numeric($this->timezone)) + { + // Might still be numeric by chance + $this->timezone = sprintf('Etc/GMT%+d', ($this->timezone + ($this->data['user_id'] != ANONYMOUS ? $this->data['user_dst'] : $config['board_dst']))); + } + + $this->timezone = new DateTimeZone($this->timezone); + // We include common language file here to not load it every time a custom language file is included $lang = &$this->lang; @@ -2072,70 +2086,17 @@ class user extends session */ function format_date($gmepoch, $format = false, $forcedate = false) { - static $midnight; - static $date_cache; + static $utc; - $format = (!$format) ? $this->date_format : $format; - $now = time(); - $delta = $now - $gmepoch; - - if (!isset($date_cache[$format])) + if (!isset($utc)) { - // Is the user requesting a friendly date format (i.e. 'Today 12:42')? - $date_cache[$format] = array( - 'is_short' => strpos($format, '|'), - 'format_short' => substr($format, 0, strpos($format, '|')) . '||' . substr(strrchr($format, '|'), 1), - 'format_long' => str_replace('|', '', $format), - 'lang' => $this->lang['datetime'], - ); - - // Short representation of month in format? Some languages use different terms for the long and short format of May - if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) - { - $date_cache[$format]['lang']['May'] = $this->lang['datetime']['May_short']; - } + $utc = new DateTimeZone('UTC'); } - // Zone offset - $zone_offset = $this->timezone + $this->dst; + $time = new phpbb_DateTime("@$gmepoch", $utc, $this); + $time->setTimezone($this->tz); - // Show date <= 1 hour ago as 'xx min ago' but not greater than 60 seconds in the future - // A small tolerence is given for times in the future but in the same minute are displayed as '< than a minute ago' - if ($delta <= 3600 && $delta > -60 && ($delta >= -5 || (($now / 60) % 60) == (($gmepoch / 60) % 60)) && $date_cache[$format]['is_short'] !== false && !$forcedate && isset($this->lang['datetime']['AGO'])) - { - return $this->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); - } - - if (!$midnight) - { - list($d, $m, $y) = explode(' ', gmdate('j n Y', time() + $zone_offset)); - $midnight = gmmktime(0, 0, 0, $m, $d, $y) - $zone_offset; - } - - if ($date_cache[$format]['is_short'] !== false && !$forcedate && !($gmepoch < $midnight - 86400 || $gmepoch > $midnight + 172800)) - { - $day = false; - - if ($gmepoch > $midnight + 86400) - { - $day = 'TOMORROW'; - } - else if ($gmepoch > $midnight) - { - $day = 'TODAY'; - } - else if ($gmepoch > $midnight - 86400) - { - $day = 'YESTERDAY'; - } - - if ($day !== false) - { - return str_replace('||', $this->lang['datetime'][$day], strtr(@gmdate($date_cache[$format]['format_short'], $gmepoch + $zone_offset), $date_cache[$format]['lang'])); - } - } - - return strtr(@gmdate($date_cache[$format]['format_long'], $gmepoch + $zone_offset), $date_cache[$format]['lang']); + return $time->format($format, $forcedate); } /** From b2a812e36bc90d01f21da469823f4e759294b770 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Tue, 20 Apr 2010 21:01:20 +0100 Subject: [PATCH 0016/1142] [feature/new-tz-handling] Correct capitalisation of phpbb_datetime. PHPBB3-9558 --- phpBB/includes/session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index ba9136b568..d8204e41f6 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -2093,7 +2093,7 @@ class user extends session $utc = new DateTimeZone('UTC'); } - $time = new phpbb_DateTime("@$gmepoch", $utc, $this); + $time = new phpbb_datetime("@$gmepoch", $utc, $this); $time->setTimezone($this->tz); return $time->format($format, $forcedate); From 9e1812a0ca728aee69df9ec5b86ea21756d3b1cc Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 21:31:41 +0100 Subject: [PATCH 0017/1142] [feature/new-tz-handling] Remove old user::$dst property PHPBB3-9558 --- phpBB/includes/session.php | 1 - 1 file changed, 1 deletion(-) diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index d8204e41f6..fe690a1a9a 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -1529,7 +1529,6 @@ class user extends session var $theme = array(); var $date_format; var $timezone; - var $dst; var $lang_name = false; var $lang_id = false; From 6a783b843b596c46738d76f2db5d539d8f68a815 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:17:40 +0100 Subject: [PATCH 0018/1142] [feature/new-tz-handling] Replace user::$timezone with user::$tz. user::$tz will store the new DateTimeZone object representing the users timezone instead of the existing user::$timezone and user::$dst combination. PHPBB3-9558 --- phpBB/includes/session.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index fe690a1a9a..2352e6394a 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -1528,7 +1528,14 @@ class user extends session var $help = array(); var $theme = array(); var $date_format; - var $timezone; + + /** + * Users current timezone + * + * @var DateTimeZone Timezone of the user + * @since 3.1 + */ + public $tz; var $lang_name = false; var $lang_id = false; @@ -1586,13 +1593,13 @@ class user extends session $this->lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); $this->date_format = $this->data['user_dateformat']; - $this->timezone = $this->data['user_timezone']; + $this->tz = $this->data['user_timezone']; } else { $this->lang_name = basename($config['default_lang']); $this->date_format = $config['default_dateformat']; - $this->timezone = $config['board_timezone']; + $this->tz = $config['board_timezone']; /** * If a guest user is surfing, we try to guess his/her language first by obtaining the browser language @@ -1631,13 +1638,13 @@ class user extends session */ } - if (is_numeric($this->timezone)) + if (is_numeric($this->tz)) { // Might still be numeric by chance - $this->timezone = sprintf('Etc/GMT%+d', ($this->timezone + ($this->data['user_id'] != ANONYMOUS ? $this->data['user_dst'] : $config['board_dst']))); + $this->tz = sprintf('Etc/GMT%+d', ($this->tz + ($this->data['user_id'] != ANONYMOUS ? $this->data['user_dst'] : $config['board_dst']))); } - $this->timezone = new DateTimeZone($this->timezone); + $this->tz = new DateTimeZone($this->tz); // We include common language file here to not load it every time a custom language file is included $lang = &$this->lang; From 74be23a098ec222cf3f3d14d6b6df236c58e8c01 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:20:08 +0100 Subject: [PATCH 0019/1142] [feature/new-tz-handling] Added a user::create_datetime() method. New method which handles instantiating new phpbb_datetime objects in the context of the current user. PHPBB3-9558 --- phpBB/includes/session.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index 2352e6394a..e91606878a 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -2105,6 +2105,21 @@ class user extends session return $time->format($format, $forcedate); } + /** + * Create a phpbb_datetime object in the context of the current user + * + * @since 3.1 + * @param string $time String in a format accepted by strtotime(). + * @param DateTimeZone $timezone Time zone of the time. + * @return phpbb_datetime Date time object linked to the current users locale + */ + public function create_datetime($time, DateTimeZone $timezone = null) + { + $timezone = $timezone ? $timezone : $this->tz; + + return new phpbb_datetime($time, $timezone, $this); + } + /** * Get language id currently used by the user */ From 2e7d9ec805a8f2088ceebf22dbc97b89a72f76d0 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 23:46:20 +0100 Subject: [PATCH 0020/1142] [feature/new-tz-handling] Fixed bug with signature of user::create_datetime(). First argument to user::create_datetime() should be optional. PHPBB3-9558 --- phpBB/includes/session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index e91606878a..cf2efd2960 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -2113,7 +2113,7 @@ class user extends session * @param DateTimeZone $timezone Time zone of the time. * @return phpbb_datetime Date time object linked to the current users locale */ - public function create_datetime($time, DateTimeZone $timezone = null) + public function create_datetime($time = 'now', DateTimeZone $timezone = null) { $timezone = $timezone ? $timezone : $this->tz; From 522f65d079c61e9ea7f718b67e37a9e58968b0f0 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:21:40 +0100 Subject: [PATCH 0021/1142] [feature/new-tz-handling] Correct typo in member comment. PHPBB3-9558 --- phpBB/includes/datetime.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index 92aef88599..3a7fc04105 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -24,7 +24,7 @@ class phpbb_datetime extends DateTime protected $_user; /** - * @var array Date formats are preprocessed by phpBB, to save constact recalculation they are cached. + * @var array Date formats are preprocessed by phpBB, to save constant recalculation they are cached. */ static protected $format_cache = array(); From c521ef1591022e69fe952ec23e6614ae36dee2e2 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:22:38 +0100 Subject: [PATCH 0022/1142] [feature/new-tz-handling] Comment and optimise phpbb_datetime::format(). - Added comments explaining the complex time computations for rendering relative date times. - Replaced some repeated method invokations with variables. PHPBB3-9558 --- phpBB/includes/datetime.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index 3a7fc04105..577081e40b 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -74,11 +74,18 @@ class phpbb_datetime extends DateTime $format = $format ? $format : $this->_user->date_format; $relative = (strpos($format, self::RELATIVE_WRAPPER) !== false && !$force_absolute); $now = new self('now', $this->_user->tz, $this->_user); - $delta = $now->getTimestamp() - $this->getTimestamp(); + + $timestamp = $this->getTimestamp(); + $now_ts = $now->getTimeStamp(); + + $delta = $now_ts - $timestamp; if ($relative) { - if ($delta <= 3600 && ($delta >= -5 || (($now->getTimestamp() / 60) % 60) == (($this->getTimestamp() / 60) % 60)) && isset($this->_user->lang['datetime']['AGO'])) + // Check the delta is less than or equal to 1 hour + // and the delta is either greater than -5 seconds or timestamp and current time are of the same minute (they must be in the same hour already) + // finally check that relative dates are supported by the language pack + if ($delta <= 3600 && ($delta >= -5 || (($now_ts / 60) % 60) == (($timestamp / 60) % 60)) && isset($this->_user->lang['datetime']['AGO'])) { return $this->_user->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); } @@ -88,7 +95,6 @@ class phpbb_datetime extends DateTime $midnight->setTime(0, 0, 0); $midnight = $midnight->getTimestamp(); - $timestamp = $this->getTimestamp(); if ($timestamp > $midnight + 86400) { @@ -107,6 +113,7 @@ class phpbb_datetime extends DateTime { $format = self::_format_cache($format, $this->_user); + // Format using the short formatting and finally swap out the relative token placeholder with the correct value return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->_user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang'])); } } From dba89a534120d48d0ba901aedd69f962536bf36d Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:24:43 +0100 Subject: [PATCH 0023/1142] [feature/new-tz-handling] Added phpbb_datetime::__toString(). New phpbb_datetime::__toString() magic method that formats the datetime according to the users default settings. PHPBB3-9558 --- phpBB/includes/datetime.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index 577081e40b..8763697c78 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -124,6 +124,16 @@ class phpbb_datetime extends DateTime return strtr(parent::format($format['format_long']), $format['lang']); } + /** + * Magic method to convert DateTime object to string + * + * @return Formatted date time, according to the users default settings. + */ + public function __toString() + { + return $this->format(); + } + /** * Pre-processes the specified date format * From f085735ef812a57790699a6d789cbab22f882328 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:25:52 +0100 Subject: [PATCH 0024/1142] [feature/new-tz-handling] Explained name of phpbb_datetime::getTimestamp() phpbb_datetime::getTimestamp() exists purely to support PHP 5.2 which does not implement the method. PHPBB3-9558 --- phpBB/includes/datetime.php | 1 + 1 file changed, 1 insertion(+) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index 8763697c78..12d04d5ca6 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -48,6 +48,7 @@ class phpbb_datetime extends DateTime /** * Returns a UNIX timestamp representation of the date time. * + * @internal This method is for backwards compatibility with 5.2, hence why it doesn't use our method naming standards. * @return int UNIX timestamp */ public function getTimestamp() From 8fe46175aff1d9275b7337a8ced8060ff850d153 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:29:36 +0100 Subject: [PATCH 0025/1142] [feature/new-tz-handling] Fix undefined variable. PHPBB3-9558 --- phpBB/includes/datetime.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index 12d04d5ca6..25da6401d7 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -97,6 +97,8 @@ class phpbb_datetime extends DateTime $midnight = $midnight->getTimestamp(); + $day = false; + if ($timestamp > $midnight + 86400) { $day = 'TOMORROW'; From 5dd7916c496b76ab2e1e28b804069dde63f7dbf0 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:47:34 +0100 Subject: [PATCH 0026/1142] [feature/new-tz-handling] Check the is_short flag stored inside the format array. Reuse the existing check store in the format array to determine if the date time format supports relative formatting. PHPBB3-9558 --- phpBB/includes/datetime.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index 25da6401d7..ecb3dfcf17 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -73,7 +73,8 @@ class phpbb_datetime extends DateTime public function format($format = '', $force_absolute = false) { $format = $format ? $format : $this->_user->date_format; - $relative = (strpos($format, self::RELATIVE_WRAPPER) !== false && !$force_absolute); + $format = self::_format_cache($format, $this->_user); + $relative = ($format['is_short'] && !$force_absolute); $now = new self('now', $this->_user->tz, $this->_user); $timestamp = $this->getTimestamp(); @@ -114,8 +115,6 @@ class phpbb_datetime extends DateTime if ($day !== false) { - $format = self::_format_cache($format, $this->_user); - // Format using the short formatting and finally swap out the relative token placeholder with the correct value return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->_user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang'])); } From e9fe9ea5185679e9950e330c949cc2577bfea21d Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:52:04 +0100 Subject: [PATCH 0027/1142] [feature/new-tz-handling] Fix bug from 3.0 formatting future dates. Future dates can get formatted as 'less than a minute ago' if they occur in the future on the same minute as the current minute. PHPBB3-9558 PHPBB3-9712 --- phpBB/includes/datetime.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index ecb3dfcf17..2276d36413 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -85,9 +85,10 @@ class phpbb_datetime extends DateTime if ($relative) { // Check the delta is less than or equal to 1 hour + // and the delta not more than a minute in the past // and the delta is either greater than -5 seconds or timestamp and current time are of the same minute (they must be in the same hour already) // finally check that relative dates are supported by the language pack - if ($delta <= 3600 && ($delta >= -5 || (($now_ts / 60) % 60) == (($timestamp / 60) % 60)) && isset($this->_user->lang['datetime']['AGO'])) + if ($delta <= 3600 && $delta > -60 && ($delta >= -5 || (($now_ts / 60) % 60) == (($timestamp / 60) % 60)) && isset($this->_user->lang['datetime']['AGO'])) { return $this->_user->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); } From 6e1278655ff3f250b4f1e09aa8674bae8d86287a Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 22:57:43 +0100 Subject: [PATCH 0028/1142] [feature/new-tz-handling] Removed line that was missed in cc312d8. PHPBB3-9558 --- phpBB/includes/datetime.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index 2276d36413..22b7f871fd 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -122,8 +122,6 @@ class phpbb_datetime extends DateTime } } - $format = self::_format_cache($format, $this->_user); - return strtr(parent::format($format['format_long']), $format['lang']); } From af789040b8880f908df0a26d5239d07d77c34124 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 23:23:11 +0100 Subject: [PATCH 0029/1142] [feature/new-tz-handling] Modify database schemas. - Dropped the user_dst column which is no longer required. - Modified the user_timezone column to take a string, max length 100. PHPBB3-9558 --- phpBB/develop/create_schema_files.php | 3 +-- phpBB/install/schemas/firebird_schema.sql | 3 +-- phpBB/install/schemas/mssql_schema.sql | 3 +-- phpBB/install/schemas/mysql_40_schema.sql | 3 +-- phpBB/install/schemas/mysql_41_schema.sql | 3 +-- phpBB/install/schemas/oracle_schema.sql | 3 +-- phpBB/install/schemas/postgres_schema.sql | 3 +-- phpBB/install/schemas/sqlite_schema.sql | 3 +-- 8 files changed, 8 insertions(+), 16 deletions(-) diff --git a/phpBB/develop/create_schema_files.php b/phpBB/develop/create_schema_files.php index 1735bffef5..f16675385b 100644 --- a/phpBB/develop/create_schema_files.php +++ b/phpBB/develop/create_schema_files.php @@ -1823,8 +1823,7 @@ function get_schema_struct() 'user_inactive_time' => array('TIMESTAMP', 0), 'user_posts' => array('UINT', 0), 'user_lang' => array('VCHAR:30', ''), - 'user_timezone' => array('DECIMAL', 0), - 'user_dst' => array('BOOL', 0), + 'user_timezone' => array('VCHAR:100', 'UTC'), 'user_dateformat' => array('VCHAR_UNI:30', 'd M Y H:i'), 'user_style' => array('UINT', 0), 'user_rank' => array('UINT', 0), diff --git a/phpBB/install/schemas/firebird_schema.sql b/phpBB/install/schemas/firebird_schema.sql index daeba45864..f4b1c624d7 100644 --- a/phpBB/install/schemas/firebird_schema.sql +++ b/phpBB/install/schemas/firebird_schema.sql @@ -1290,8 +1290,7 @@ CREATE TABLE phpbb_users ( user_inactive_time INTEGER DEFAULT 0 NOT NULL, user_posts INTEGER DEFAULT 0 NOT NULL, user_lang VARCHAR(30) CHARACTER SET NONE DEFAULT '' NOT NULL, - user_timezone DOUBLE PRECISION DEFAULT 0 NOT NULL, - user_dst INTEGER DEFAULT 0 NOT NULL, + user_timezone VARCHAR(100) CHARACTER SET NONE DEFAULT 'UTC' NOT NULL, user_dateformat VARCHAR(30) CHARACTER SET UTF8 DEFAULT 'd M Y H:i' NOT NULL COLLATE UNICODE, user_style INTEGER DEFAULT 0 NOT NULL, user_rank INTEGER DEFAULT 0 NOT NULL, diff --git a/phpBB/install/schemas/mssql_schema.sql b/phpBB/install/schemas/mssql_schema.sql index 736917fdcb..fc1afcbbff 100644 --- a/phpBB/install/schemas/mssql_schema.sql +++ b/phpBB/install/schemas/mssql_schema.sql @@ -1576,8 +1576,7 @@ CREATE TABLE [phpbb_users] ( [user_inactive_time] [int] DEFAULT (0) NOT NULL , [user_posts] [int] DEFAULT (0) NOT NULL , [user_lang] [varchar] (30) DEFAULT ('') NOT NULL , - [user_timezone] [float] DEFAULT (0) NOT NULL , - [user_dst] [int] DEFAULT (0) NOT NULL , + [user_timezone] [varchar] (100) DEFAULT ('UTC') NOT NULL , [user_dateformat] [varchar] (30) DEFAULT ('d M Y H:i') NOT NULL , [user_style] [int] DEFAULT (0) NOT NULL , [user_rank] [int] DEFAULT (0) NOT NULL , diff --git a/phpBB/install/schemas/mysql_40_schema.sql b/phpBB/install/schemas/mysql_40_schema.sql index 97c378621b..76b9294987 100644 --- a/phpBB/install/schemas/mysql_40_schema.sql +++ b/phpBB/install/schemas/mysql_40_schema.sql @@ -914,8 +914,7 @@ CREATE TABLE phpbb_users ( user_inactive_time int(11) UNSIGNED DEFAULT '0' NOT NULL, user_posts mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, user_lang varbinary(30) DEFAULT '' NOT NULL, - user_timezone decimal(5,2) DEFAULT '0' NOT NULL, - user_dst tinyint(1) UNSIGNED DEFAULT '0' NOT NULL, + user_timezone varbinary(100) DEFAULT 'UTC' NOT NULL, user_dateformat varbinary(90) DEFAULT 'd M Y H:i' NOT NULL, user_style mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, user_rank mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, diff --git a/phpBB/install/schemas/mysql_41_schema.sql b/phpBB/install/schemas/mysql_41_schema.sql index 9615905625..30fb7a8623 100644 --- a/phpBB/install/schemas/mysql_41_schema.sql +++ b/phpBB/install/schemas/mysql_41_schema.sql @@ -914,8 +914,7 @@ CREATE TABLE phpbb_users ( user_inactive_time int(11) UNSIGNED DEFAULT '0' NOT NULL, user_posts mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, user_lang varchar(30) DEFAULT '' NOT NULL, - user_timezone decimal(5,2) DEFAULT '0' NOT NULL, - user_dst tinyint(1) UNSIGNED DEFAULT '0' NOT NULL, + user_timezone varchar(100) DEFAULT 'UTC' NOT NULL, user_dateformat varchar(30) DEFAULT 'd M Y H:i' NOT NULL, user_style mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, user_rank mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, diff --git a/phpBB/install/schemas/oracle_schema.sql b/phpBB/install/schemas/oracle_schema.sql index 5d60d2a19e..a4b8686948 100644 --- a/phpBB/install/schemas/oracle_schema.sql +++ b/phpBB/install/schemas/oracle_schema.sql @@ -1702,8 +1702,7 @@ CREATE TABLE phpbb_users ( user_inactive_time number(11) DEFAULT '0' NOT NULL, user_posts number(8) DEFAULT '0' NOT NULL, user_lang varchar2(30) DEFAULT '' , - user_timezone number(5, 2) DEFAULT '0' NOT NULL, - user_dst number(1) DEFAULT '0' NOT NULL, + user_timezone varchar2(100) DEFAULT 'UTC' NOT NULL, user_dateformat varchar2(90) DEFAULT 'd M Y H:i' NOT NULL, user_style number(8) DEFAULT '0' NOT NULL, user_rank number(8) DEFAULT '0' NOT NULL, diff --git a/phpBB/install/schemas/postgres_schema.sql b/phpBB/install/schemas/postgres_schema.sql index d7377ac2e6..7138c818ff 100644 --- a/phpBB/install/schemas/postgres_schema.sql +++ b/phpBB/install/schemas/postgres_schema.sql @@ -1172,8 +1172,7 @@ CREATE TABLE phpbb_users ( user_inactive_time INT4 DEFAULT '0' NOT NULL CHECK (user_inactive_time >= 0), user_posts INT4 DEFAULT '0' NOT NULL CHECK (user_posts >= 0), user_lang varchar(30) DEFAULT '' NOT NULL, - user_timezone decimal(5,2) DEFAULT '0' NOT NULL, - user_dst INT2 DEFAULT '0' NOT NULL CHECK (user_dst >= 0), + user_timezone varchar(100) DEFAULT 'UTC' NOT NULL, user_dateformat varchar(30) DEFAULT 'd M Y H:i' NOT NULL, user_style INT4 DEFAULT '0' NOT NULL CHECK (user_style >= 0), user_rank INT4 DEFAULT '0' NOT NULL CHECK (user_rank >= 0), diff --git a/phpBB/install/schemas/sqlite_schema.sql b/phpBB/install/schemas/sqlite_schema.sql index 257937275c..5066ac97df 100644 --- a/phpBB/install/schemas/sqlite_schema.sql +++ b/phpBB/install/schemas/sqlite_schema.sql @@ -886,8 +886,7 @@ CREATE TABLE phpbb_users ( user_inactive_time INTEGER UNSIGNED NOT NULL DEFAULT '0', user_posts INTEGER UNSIGNED NOT NULL DEFAULT '0', user_lang varchar(30) NOT NULL DEFAULT '', - user_timezone decimal(5,2) NOT NULL DEFAULT '0', - user_dst INTEGER UNSIGNED NOT NULL DEFAULT '0', + user_timezone varchar(100) NOT NULL DEFAULT 'UTC', user_dateformat varchar(30) NOT NULL DEFAULT 'd M Y H:i', user_style INTEGER UNSIGNED NOT NULL DEFAULT '0', user_rank INTEGER UNSIGNED NOT NULL DEFAULT '0', From 1665434853fb09e70337d23955e1c9a5f3f0d19d Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 7 Jul 2010 23:42:54 +0100 Subject: [PATCH 0030/1142] [feature/new-tz-handling] Remove code using legacy timezone properties. Code accessing the legacy user::$timezone and user::$dst properties has been removed and replaced with code utilising user::create_datetime(). Changed by Oleg: in viewtopic, memberlist and index use getTimestamp() + getOffset(). We show members that have birthdays on the specified date. getTimestamp() returns the current date in UTC. We add getOffset() to obtain the current local time in the viewing user's timezone. Then we find members having birthday on this date. Changed by Oleg again: Take leap year status out of the datetime object we have, this seems like it should work as one would expect. PHPBB3-9558 --- phpBB/feed.php | 2 +- phpBB/includes/functions_profile_fields.php | 9 ++++++--- phpBB/index.php | 5 +++-- phpBB/memberlist.php | 3 ++- phpBB/viewtopic.php | 3 ++- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/phpBB/feed.php b/phpBB/feed.php index b8c0c370f9..d7092d6758 100644 --- a/phpBB/feed.php +++ b/phpBB/feed.php @@ -255,7 +255,7 @@ function feed_format_date($time) { global $user; - $zone_offset = (int) $user->timezone + (int) $user->dst; + $zone_offset = $user->create_datetime()->getOffset(); $sign = ($zone_offset < 0) ? '-' : '+'; $time_offset = abs($zone_offset); diff --git a/phpBB/includes/functions_profile_fields.php b/phpBB/includes/functions_profile_fields.php index ec29a1732d..aacfa82c54 100644 --- a/phpBB/includes/functions_profile_fields.php +++ b/phpBB/includes/functions_profile_fields.php @@ -555,9 +555,12 @@ class custom_profile else if ($day && $month && $year) { global $user; - // Date should display as the same date for every user regardless of timezone, so remove offset - // to compensate for the offset added by user::format_date() - return $user->format_date(gmmktime(0, 0, 0, $month, $day, $year) - ($user->timezone + $user->dst), $user->lang['DATE_FORMAT'], true); + // Date should display as the same date for every user regardless of timezone + + return $user->create_datetime() + ->setDate($year, $month, $day) + ->setTime(0, 0, 0) + ->format($user->lang['DATE_FORMAT'], true); } return $value; diff --git a/phpBB/index.php b/phpBB/index.php index 182efbc7e0..ccefd9833c 100644 --- a/phpBB/index.php +++ b/phpBB/index.php @@ -84,11 +84,12 @@ $legend = implode(', ', $legend); $birthday_list = array(); if ($config['load_birthdays'] && $config['allow_birthdays'] && $auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) { - $now = phpbb_gmgetdate(time() + $user->timezone + $user->dst); + $time = $user->create_datetime(); + $now = phpbb_gmgetdate($time->getTimestamp() + $time->getOffset()); // Display birthdays of 29th february on 28th february in non-leap-years $leap_year_birthdays = ''; - if ($now['mday'] == 28 && $now['mon'] == 2 && !$user->format_date(time(), 'L')) + if ($now['mday'] == 28 && $now['mon'] == 2 && !$time->format('L')) { $leap_year_birthdays = " OR user_birthday LIKE '" . $db->sql_escape(sprintf('%2d-%2d-', 29, 2)) . "%'"; } diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 556db2fa5d..ea8a6fc44b 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -1684,7 +1684,8 @@ function show_profile($data, $user_notes_enabled = false, $warn_user_enabled = f if ($bday_year) { - $now = phpbb_gmgetdate(time() + $user->timezone + $user->dst); + $now = $user->create_datetime(); + $now = phpbb_gmgetdate($now->getTimestamp() + $now->getOffset()); $diff = $now['mon'] - $bday_month; if ($diff == 0) diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index e78ba73cd7..782f02fd4b 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -965,7 +965,8 @@ $sql_ary = array( $sql = $db->sql_build_query('SELECT', $sql_ary); $result = $db->sql_query($sql); -$now = phpbb_gmgetdate(time() + $user->timezone + $user->dst); +$now = $user->create_datetime(); +$now = phpbb_gmgetdate($now->getTimestamp() + $now->getOffset()); // Posts are stored in the $rowset array while $attach_list, $user_cache // and the global bbcode_bitfield are built From 0f320a6c48d63154bba45c2937adeee79165816c Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 8 Jul 2010 21:56:51 +0100 Subject: [PATCH 0031/1142] [feature/new-tz-handling] Update tz_select() to use the PHP timezone database. tz_select() now uses the PHP timezone database to generate the timezone selection box, it tries to use a translated language string otherwise falls back to a label produced from the timezone identifier. I've done this so new timezones are available immediately without a new language pack. PHPBB3-9558 --- phpBB/includes/functions.php | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 9e2e57dd5e..fb8ea93e32 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1071,28 +1071,45 @@ function style_select($default = '', $all = false) /** * Pick a timezone +* @todo Possible HTML escaping */ function tz_select($default = '', $truncate = false) { global $user; - $tz_select = ''; - foreach ($user->lang['tz_zones'] as $offset => $zone) + static $timezones; + + if (!isset($timezones)) { - if ($truncate) + $timezones = DateTimeZone::listIdentifiers(); + + sort($timezones); + } + + $tz_select = ''; + + foreach ($timezones as $timezone) + { + if (isset($user->lang['timezones'][$timezone])) { - $zone_trunc = truncate_string($zone, 50, 255, false, '...'); + $title = $label = $user->lang['timezones'][$timezone]; } else { - $zone_trunc = $zone; + // No label, we'll figure one out + // @todo rtl languages? + $bits = explode('/', strtolower(str_replace('_', ' ', $timezone))); + + $title = $label = ucwords(implode(' - ', $bits)); } - if (is_numeric($offset)) + if ($truncate) { - $selected = ($offset == $default) ? ' selected="selected"' : ''; - $tz_select .= ''; + $label = truncate_string($label, 50, 255, false, '...'); } + + $selected = ($timezone === $default) ? ' selected="selected"' : ''; + $tz_select .= ''; } return $tz_select; From 190b019fa28f59c018554916e33446d93efb7311 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 8 Jul 2010 22:09:24 +0100 Subject: [PATCH 0032/1142] [feature/new-tz-handling] Remove case mangling, the identifiers are correct. PHPBB3-9558 --- phpBB/includes/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index fb8ea93e32..69bfe3e090 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1098,9 +1098,9 @@ function tz_select($default = '', $truncate = false) { // No label, we'll figure one out // @todo rtl languages? - $bits = explode('/', strtolower(str_replace('_', ' ', $timezone))); + $bits = explode('/', str_replace('_', ' ', $timezone)); - $title = $label = ucwords(implode(' - ', $bits)); + $title = $label = implode(' - ', $bits); } if ($truncate) From f17664a00c9fa3b80a887bde2cdb2424a5d5567a Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Fri, 9 Jul 2010 20:39:30 +0100 Subject: [PATCH 0033/1142] [feature/new-tz-handling] Correct a bug preventing multiple formats working. PHPBB3-9558 --- phpBB/includes/datetime.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index 22b7f871fd..15e3c8b0b7 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -149,22 +149,22 @@ class phpbb_datetime extends DateTime if (!isset(self::$format_cache[$lang])) { self::$format_cache[$lang] = array(); + } - if (!isset(self::$format_cache[$lang][$format])) + if (!isset(self::$format_cache[$lang][$format])) + { + // Is the user requesting a friendly date format (i.e. 'Today 12:42')? + self::$format_cache[$lang][$format] = array( + 'is_short' => strpos($format, self::RELATIVE_WRAPPER) !== false, + 'format_short' => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1), + 'format_long' => str_replace(self::RELATIVE_WRAPPER, '', $format), + 'lang' => $user->lang['datetime'], + ); + + // Short representation of month in format? Some languages use different terms for the long and short format of May + if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) { - // Is the user requesting a friendly date format (i.e. 'Today 12:42')? - self::$format_cache[$lang][$format] = array( - 'is_short' => strpos($format, self::RELATIVE_WRAPPER) !== false, - 'format_short' => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1), - 'format_long' => str_replace(self::RELATIVE_WRAPPER, '', $format), - 'lang' => $user->lang['datetime'], - ); - - // Short representation of month in format? Some languages use different terms for the long and short format of May - if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) - { - self::$format_cache[$lang][$format]['lang']['May'] = $user->lang['datetime']['May_short']; - } + self::$format_cache[$lang][$format]['lang']['May'] = $user->lang['datetime']['May_short']; } } From bb461c8daa97358e8bcce923a21eba0abd6ea3dc Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 10 Mar 2011 19:14:53 -0500 Subject: [PATCH 0034/1142] [feature/new-tz-handling] Sort timezones in selector by offset. Since the list of timezones is very long, and users are likely to know their current offset but not necessarily which city that is nearby is in the timezone database, sort the list of timezones by offset. UTC is specially handled to show up before other GMT+0 timezones. PHPBB3-9558 --- phpBB/feed.php | 10 +---- phpBB/includes/functions.php | 71 +++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/phpBB/feed.php b/phpBB/feed.php index d7092d6758..025904e682 100644 --- a/phpBB/feed.php +++ b/phpBB/feed.php @@ -256,15 +256,7 @@ function feed_format_date($time) global $user; $zone_offset = $user->create_datetime()->getOffset(); - - $sign = ($zone_offset < 0) ? '-' : '+'; - $time_offset = abs($zone_offset); - - $offset_seconds = $time_offset % 3600; - $offset_minutes = $offset_seconds / 60; - $offset_hours = ($time_offset - $offset_seconds) / 3600; - - $offset_string = sprintf("%s%02d:%02d", $sign, $offset_hours, $offset_minutes); + $offset_string = phpbb_format_timezone_offset($zone_offset); } return gmdate("Y-m-d\TH:i:s", $time + $zone_offset) . $offset_string; diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 69bfe3e090..31a191b513 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1069,6 +1069,65 @@ function style_select($default = '', $all = false) return $style_options; } +function phpbb_format_timezone_offset($tz_offset) +{ + $sign = ($tz_offset < 0) ? '-' : '+'; + $time_offset = abs($tz_offset); + + $offset_seconds = $time_offset % 3600; + $offset_minutes = $offset_seconds / 60; + $offset_hours = ($time_offset - $offset_seconds) / 3600; + + $offset_string = sprintf("%s%02d:%02d", $sign, $offset_hours, $offset_minutes); + return $offset_string; +} + +// Compares two time zone labels. +// Arranges them in increasing order by timezone offset. +// Places UTC before other timezones in the same offset. +function tz_select_compare($a, $b) +{ + $a_sign = $a[3]; + $b_sign = $b[3]; + if ($a_sign != $b_sign) + { + return $a_sign == '-' ? -1 : 1; + } + + $a_offset = substr($a, 4, 5); + $b_offset = substr($b, 4, 5); + if ($a_offset == $b_offset) + { + $a_name = substr($a, 12); + $b_name = substr($b, 12); + if ($a_name == $b_name) + { + return 0; + } else if ($a_name == 'UTC') + { + return -1; + } else if ($b_name == 'UTC') + { + return 1; + } + else + { + return $a_name < $b_name ? -1 : 1; + } + } + else + { + if ($a_sign == '-') + { + return $a_offset > $b_offset ? -1 : 1; + } + else + { + return $a_offset < $b_offset ? -1 : 1; + } + } +} + /** * Pick a timezone * @todo Possible HTML escaping @@ -1083,7 +1142,17 @@ function tz_select($default = '', $truncate = false) { $timezones = DateTimeZone::listIdentifiers(); - sort($timezones); + foreach ($timezones as &$timezone) + { + $tz = new DateTimeZone($timezone); + $dt = new phpbb_datetime('now', $tz); + $offset = $dt->getOffset(); + $offset_string = phpbb_format_timezone_offset($offset); + $timezone = 'GMT' . $offset_string . ' - ' . $timezone; + } + unset($timezone); + + usort($timezones, 'tz_select_compare'); } $tz_select = ''; From da8009603d05acdccaff15cd610353d85c365220 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 15 Mar 2011 07:45:43 -0400 Subject: [PATCH 0035/1142] [feature/new-tz-handling] Preselect a timezone in registration. Use Javascript to obtain client's timezone offset and select the first timezone in our list with that offset. Changes for prosilver only. The javascript file should be shared between styles. PHPBB3-9558 --- phpBB/language/en/ucp.php | 1 + phpBB/styles/prosilver/template/timezone.js | 54 +++++++++++++++++++ .../template/ucp_prefs_personal.html | 10 +++- .../prosilver/template/ucp_register.html | 10 +++- 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 phpBB/styles/prosilver/template/timezone.js diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index c8ffbf31c0..9cbca4b740 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -399,6 +399,7 @@ $lang = array_merge($lang, array( 'SIGNATURE_EXPLAIN' => 'This is a block of text that can be added to posts you make. There is a %d character limit.', 'SIGNATURE_PREVIEW' => 'Your signature will appear like this in posts', 'SIGNATURE_TOO_LONG' => 'Your signature is too long.', + 'SELECT_TIMEZONE' => 'Select timezone', 'SORT' => 'Sort', 'SORT_COMMENT' => 'File comment', 'SORT_DOWNLOADS' => 'Downloads', diff --git a/phpBB/styles/prosilver/template/timezone.js b/phpBB/styles/prosilver/template/timezone.js new file mode 100644 index 0000000000..5d07b1d3ad --- /dev/null +++ b/phpBB/styles/prosilver/template/timezone.js @@ -0,0 +1,54 @@ +function phpbb_preselect_tz_select() +{ + var selector = document.getElementsByClassName('tz_select')[0]; + if (selector.value) + { + return; + } + // The offset returned here is in minutes and negated. + // http://www.w3schools.com/jsref/jsref_getTimezoneOffset.asp + var offset = (new Date()).getTimezoneOffset(); + if (offset < 0) + { + var sign = '+'; + offset = -offset; + } + else + { + var sign = '-'; + } + var minutes = offset % 60; + var hours = (offset - minutes) / 60; + if (hours < 10) + { + hours = '0' + hours.toString(); + } + else + { + hours = hours.toString(); + } + if (minutes < 10) + { + minutes = '0' + minutes.toString(); + } + else + { + minutes = minutes.toString(); + } + var prefix = 'GMT' + sign + hours + ':' + minutes; + var prefix_length = prefix.length; + for (var i = 0; i < selector.options.length; ++i) + { + var option = selector.options[i]; + if (option.value.substring(0, prefix_length) == prefix) + { + // Firefox scrolls the selector only to put the option into view; + // for negative-offset timezones, this means the first timezone + // of a particular offset will be the bottom one, and selected, + // with all other timezones not visible. Not much can be done + // about that here unfortunately. + option.selected = true; + break; + } + } +} diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index 635f321469..eae37b8021 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -75,7 +75,12 @@
-
+
+ +
@@ -141,4 +146,7 @@ // ]]> + + + diff --git a/phpBB/styles/prosilver/template/ucp_register.html b/phpBB/styles/prosilver/template/ucp_register.html index dd0e5ad02a..542a860f23 100644 --- a/phpBB/styles/prosilver/template/ucp_register.html +++ b/phpBB/styles/prosilver/template/ucp_register.html @@ -59,7 +59,12 @@
-
+
+ +
@@ -110,4 +115,7 @@ + + + From 56a7038aa57a1ec473196e01f68b7c8a1d0a89b6 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 15 Mar 2011 07:56:21 -0400 Subject: [PATCH 0036/1142] [feature/new-tz-handling] Removed DST options from templates. PHPBB3-9558 --- phpBB/adm/style/acp_users_prefs.html | 5 ----- phpBB/styles/prosilver/template/ucp_prefs_personal.html | 7 ------- phpBB/styles/subsilver2/template/ucp_prefs_personal.html | 4 ---- 3 files changed, 16 deletions(-) diff --git a/phpBB/adm/style/acp_users_prefs.html b/phpBB/adm/style/acp_users_prefs.html index a519447b2f..90db62984e 100644 --- a/phpBB/adm/style/acp_users_prefs.html +++ b/phpBB/adm/style/acp_users_prefs.html @@ -56,11 +56,6 @@
-
-
-
-
-

{L_BOARD_DATE_FORMAT_EXPLAIN}
diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index eae37b8021..267a3b8c2f 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -82,13 +82,6 @@
-
-
-
- - -
-

{L_BOARD_DATE_FORMAT_EXPLAIN}
diff --git a/phpBB/styles/subsilver2/template/ucp_prefs_personal.html b/phpBB/styles/subsilver2/template/ucp_prefs_personal.html index 8e4b04fc10..357e1fe100 100644 --- a/phpBB/styles/subsilver2/template/ucp_prefs_personal.html +++ b/phpBB/styles/subsilver2/template/ucp_prefs_personal.html @@ -77,10 +77,6 @@ - - {L_BOARD_DST}: - checked="checked" /> {L_YES}   checked="checked" /> {L_NO} - {L_BOARD_DATE_FORMAT}:
{L_BOARD_DATE_FORMAT_EXPLAIN} From e8e5d2b2c000e8a91897b4044c6a3005f60b86ab Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 15 Mar 2011 17:37:15 -0400 Subject: [PATCH 0037/1142] [feature/new-tz-handling] Removed DST and numeric timezone-related language. PHPBB3-9558 --- phpBB/language/en/acp/board.php | 1 - phpBB/language/en/common.php | 87 --------------------------------- phpBB/language/en/help_faq.php | 2 +- phpBB/language/en/ucp.php | 1 - 4 files changed, 1 insertion(+), 90 deletions(-) diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index ff516a8146..0be4c0df6d 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -49,7 +49,6 @@ $lang = array_merge($lang, array( 'OVERRIDE_STYLE_EXPLAIN' => 'Replaces user’s style with the default.', 'SITE_DESC' => 'Site description', 'SITE_NAME' => 'Site name', - 'SYSTEM_DST' => 'Enable Summer Time/DST', 'SYSTEM_TIMEZONE' => 'Guest timezone', 'SYSTEM_TIMEZONE_EXPLAIN' => 'Timezone to use for displaying times to users who are not logged in (guests, bots). Logged in users set their timezone during registration and can change it in their user control panel.', 'WARNINGS_EXPIRE' => 'Warning duration', diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 44a63a6acb..bcb08a8b73 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -786,93 +786,6 @@ $lang = array_merge($lang, array( 'Dec' => 'Dec', ), - 'tz' => array( - '-12' => 'UTC - 12 hours', - '-11' => 'UTC - 11 hours', - '-10' => 'UTC - 10 hours', - '-9.5' => 'UTC - 9:30 hours', - '-9' => 'UTC - 9 hours', - '-8' => 'UTC - 8 hours', - '-7' => 'UTC - 7 hours', - '-6' => 'UTC - 6 hours', - '-5' => 'UTC - 5 hours', - '-4.5' => 'UTC - 4:30 hours', - '-4' => 'UTC - 4 hours', - '-3.5' => 'UTC - 3:30 hours', - '-3' => 'UTC - 3 hours', - '-2' => 'UTC - 2 hours', - '-1' => 'UTC - 1 hour', - '0' => 'UTC', - '1' => 'UTC + 1 hour', - '2' => 'UTC + 2 hours', - '3' => 'UTC + 3 hours', - '3.5' => 'UTC + 3:30 hours', - '4' => 'UTC + 4 hours', - '4.5' => 'UTC + 4:30 hours', - '5' => 'UTC + 5 hours', - '5.5' => 'UTC + 5:30 hours', - '5.75' => 'UTC + 5:45 hours', - '6' => 'UTC + 6 hours', - '6.5' => 'UTC + 6:30 hours', - '7' => 'UTC + 7 hours', - '8' => 'UTC + 8 hours', - '8.75' => 'UTC + 8:45 hours', - '9' => 'UTC + 9 hours', - '9.5' => 'UTC + 9:30 hours', - '10' => 'UTC + 10 hours', - '10.5' => 'UTC + 10:30 hours', - '11' => 'UTC + 11 hours', - '11.5' => 'UTC + 11:30 hours', - '12' => 'UTC + 12 hours', - '12.75' => 'UTC + 12:45 hours', - '13' => 'UTC + 13 hours', - '14' => 'UTC + 14 hours', - 'dst' => '[ DST ]', - ), - - 'tz_zones' => array( - '-12' => '[UTC - 12] Baker Island Time', - '-11' => '[UTC - 11] Niue Time, Samoa Standard Time', - '-10' => '[UTC - 10] Hawaii-Aleutian Standard Time, Cook Island Time', - '-9.5' => '[UTC - 9:30] Marquesas Islands Time', - '-9' => '[UTC - 9] Alaska Standard Time, Gambier Island Time', - '-8' => '[UTC - 8] Pacific Standard Time', - '-7' => '[UTC - 7] Mountain Standard Time', - '-6' => '[UTC - 6] Central Standard Time', - '-5' => '[UTC - 5] Eastern Standard Time', - '-4.5' => '[UTC - 4:30] Venezuelan Standard Time', - '-4' => '[UTC - 4] Atlantic Standard Time', - '-3.5' => '[UTC - 3:30] Newfoundland Standard Time', - '-3' => '[UTC - 3] Amazon Standard Time, Central Greenland Time', - '-2' => '[UTC - 2] Fernando de Noronha Time, South Georgia & the South Sandwich Islands Time', - '-1' => '[UTC - 1] Azores Standard Time, Cape Verde Time, Eastern Greenland Time', - '0' => '[UTC] Western European Time, Greenwich Mean Time', - '1' => '[UTC + 1] Central European Time, West African Time', - '2' => '[UTC + 2] Eastern European Time, Central African Time', - '3' => '[UTC + 3] Moscow Standard Time, Eastern African Time', - '3.5' => '[UTC + 3:30] Iran Standard Time', - '4' => '[UTC + 4] Gulf Standard Time, Samara Standard Time', - '4.5' => '[UTC + 4:30] Afghanistan Time', - '5' => '[UTC + 5] Pakistan Standard Time, Yekaterinburg Standard Time', - '5.5' => '[UTC + 5:30] Indian Standard Time, Sri Lanka Time', - '5.75' => '[UTC + 5:45] Nepal Time', - '6' => '[UTC + 6] Bangladesh Time, Bhutan Time, Novosibirsk Standard Time', - '6.5' => '[UTC + 6:30] Cocos Islands Time, Myanmar Time', - '7' => '[UTC + 7] Indochina Time, Krasnoyarsk Standard Time', - '8' => '[UTC + 8] Chinese Standard Time, Australian Western Standard Time, Irkutsk Standard Time', - '8.75' => '[UTC + 8:45] Southeastern Western Australia Standard Time', - '9' => '[UTC + 9] Japan Standard Time, Korea Standard Time, Chita Standard Time', - '9.5' => '[UTC + 9:30] Australian Central Standard Time', - '10' => '[UTC + 10] Australian Eastern Standard Time, Vladivostok Standard Time', - '10.5' => '[UTC + 10:30] Lord Howe Standard Time', - '11' => '[UTC + 11] Solomon Island Time, Magadan Standard Time', - '11.5' => '[UTC + 11:30] Norfolk Island Time', - '12' => '[UTC + 12] New Zealand Time, Fiji Time, Kamchatka Standard Time', - '12.75' => '[UTC + 12:45] Chatham Islands Time', - '13' => '[UTC + 13] Tonga Time, Phoenix Islands Time', - '14' => '[UTC + 14] Line Island Time', - ), - // The value is only an example and will get replaced by the current time on view 'dateformats' => array( 'd M Y, H:i' => '01 Jan 2007, 13:37', diff --git a/phpBB/language/en/help_faq.php b/phpBB/language/en/help_faq.php index 6ca9589913..5f9af2d721 100644 --- a/phpBB/language/en/help_faq.php +++ b/phpBB/language/en/help_faq.php @@ -88,7 +88,7 @@ $help = array( ), array( 0 => 'I changed the timezone and the time is still wrong!', - 1 => 'If you are sure you have set the timezone and Summer Time/DST correctly and the time is still incorrect, then the time stored on the server clock is incorrect. Please notify an administrator to correct the problem.' + 1 => 'If you are sure you have set the timezone correctly and the time is still incorrect, then the time stored on the server clock is incorrect. Please notify an administrator to correct the problem.' ), array( 0 => 'My language is not in the list!', diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 9cbca4b740..3925005968 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -103,7 +103,6 @@ $lang = array_merge($lang, array( 'BIRTHDAY_EXPLAIN' => 'Setting a year will list your age when it is your birthday.', 'BOARD_DATE_FORMAT' => 'My date format', 'BOARD_DATE_FORMAT_EXPLAIN' => 'The syntax used is identical to the PHP date() function.', - 'BOARD_DST' => 'Summer Time/DST is in effect', 'BOARD_LANGUAGE' => 'My language', 'BOARD_STYLE' => 'My board style', 'BOARD_TIMEZONE' => 'My timezone', From b672fc7ae113c0e01f1d7ce4ffae3eb26e57b586 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 26 Apr 2011 22:11:45 -0400 Subject: [PATCH 0038/1142] [feature/new-tz-handling] Started on database updater changes. The changes are tricky since we need to drop user_dst column from users table, but we need it during the data migration process to properly calculate effective timezones for all users. The change here converts user_timezone to vchar and computes timezone identifiers from the offsets. It uses database-specific functions for building SQL conditionals and concatenations which need to be implemented, probably in a separate ticket. As a result the current code is not functional. PHPBB3-9558 --- phpBB/install/database_update.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index fa0fba9dc7..9d550cf938 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -1090,6 +1090,9 @@ function database_update_info() GROUPS_TABLE => array( 'group_legend' => array('UINT', 0), ), + USERS_TABLE => array( + 'user_timezone' => array('VCHAR:100', ''), + ), ), 'drop_columns' => array( STYLES_TABLE => array( @@ -2352,6 +2355,25 @@ function change_database_data(&$no_updates, $version) set_config('teampage_memberships', '1'); } + // Update timezones + // user_dst is 0 if not in dst and 1 if in dst; + // this happens to be exactly the correction that should be added to the timezone offset + // to obtain dst offset. + // Parenthesize here because we operate on this value later. + $active_offset = '(user_timezone + user_dst)'; + // Now we have a tricky problem of forcing the plus sign into the expression. + // Build it via a conditional since there cannot be a portable printf equivalent in databases. + // Note that active offset is not an absolute value here - it is an expression that will + // be evaluated by the database during query execution. + // We don't print - here because it will come from active offset. + $sign = $db->conditional_sql("$active_offset < 0", '', '+'); + // Use database-specific escaping because strings are quoted differently by different databases. + $new_value = $db->concatenate_sql($db->sql_escape('GMT'), $sign, $active_offset); + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_timezone = ' . $new_value; + _sql($sql, $errored, $error_ary); + // After we have calculated the timezones we can delete user_dst column from user table. + $no_updates = false; break; } From 9915ed52551b9b11e2cfcadabcb758ab05c6db6b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 15 Mar 2012 16:07:14 +0100 Subject: [PATCH 0039/1142] =?UTF-8?q?[ticket/8743]=20Include=20poster?= =?UTF-8?q?=C2=B4s=20name=20in=20mail=20notifications?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PHPBB3-8743 --- phpBB/includes/functions_posting.php | 6 ++++-- phpBB/language/en/email/forum_notify.txt | 2 +- phpBB/language/en/email/newtopic_notify.txt | 2 +- phpBB/language/en/email/topic_notify.txt | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 77d92e26e2..be3aa844fe 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1160,7 +1160,7 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id /** * User Notification */ -function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id, $topic_id, $post_id) +function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id, $topic_id, $post_id, $author_name = '') { global $db, $user, $config, $phpbb_root_path, $phpEx, $auth; @@ -1323,6 +1323,7 @@ function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id 'USERNAME' => htmlspecialchars_decode($addr['name']), 'TOPIC_TITLE' => htmlspecialchars_decode($topic_title), 'FORUM_NAME' => htmlspecialchars_decode($forum_name), + 'AUTHOR_NAME' => htmlspecialchars_decode($author_name), 'U_FORUM' => generate_board_url() . "/viewforum.$phpEx?f=$forum_id", 'U_TOPIC' => generate_board_url() . "/viewtopic.$phpEx?f=$forum_id&t=$topic_id", @@ -2585,7 +2586,8 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u // Send Notifications if (($mode == 'reply' || $mode == 'quote' || $mode == 'post') && $post_approval) { - user_notification($mode, $subject, $data['topic_title'], $data['forum_name'], $data['forum_id'], $data['topic_id'], $data['post_id']); + $username = ($username) ? $username : $user->data['username']; + user_notification($mode, $subject, $data['topic_title'], $data['forum_name'], $data['forum_id'], $data['topic_id'], $data['post_id'], $username); } $params = $add_anchor = ''; diff --git a/phpBB/language/en/email/forum_notify.txt b/phpBB/language/en/email/forum_notify.txt index fae5a83885..490780a0a6 100644 --- a/phpBB/language/en/email/forum_notify.txt +++ b/phpBB/language/en/email/forum_notify.txt @@ -2,7 +2,7 @@ Subject: Forum post notification - "{FORUM_NAME}" Hello {USERNAME}, -You are receiving this notification because you are watching the forum, "{FORUM_NAME}" at "{SITENAME}". This forum has received a new reply to the topic "{TOPIC_TITLE}" since your last visit. You can use the following link to view the last unread reply, no more notifications will be sent until you visit the topic. +You are receiving this notification because you are watching the forum, "{FORUM_NAME}" at "{SITENAME}". This forum has received a new reply to the topic "{TOPIC_TITLE}" by {AUTHOR_NAME} since your last visit. You can use the following link to view the last unread reply, no more notifications will be sent until you visit the topic. {U_NEWEST_POST} diff --git a/phpBB/language/en/email/newtopic_notify.txt b/phpBB/language/en/email/newtopic_notify.txt index 529bbf0f8f..eda1370938 100644 --- a/phpBB/language/en/email/newtopic_notify.txt +++ b/phpBB/language/en/email/newtopic_notify.txt @@ -2,7 +2,7 @@ Subject: New topic notification - "{FORUM_NAME}" Hello {USERNAME}, -You are receiving this notification because you are watching the forum, "{FORUM_NAME}" at "{SITENAME}". This forum has received a new topic since your last visit, "{TOPIC_TITLE}". You can use the following link to view the forum, no more notifications will be sent until you visit the forum. +You are receiving this notification because you are watching the forum, "{FORUM_NAME}" at "{SITENAME}". This forum has received a new topic by {AUTHOR_NAME} since your last visit, "{TOPIC_TITLE}". You can use the following link to view the forum, no more notifications will be sent until you visit the forum. {U_FORUM} diff --git a/phpBB/language/en/email/topic_notify.txt b/phpBB/language/en/email/topic_notify.txt index 99587b28e0..fcfbcc2abd 100644 --- a/phpBB/language/en/email/topic_notify.txt +++ b/phpBB/language/en/email/topic_notify.txt @@ -2,7 +2,7 @@ Subject: Topic reply notification - "{TOPIC_TITLE}" Hello {USERNAME}, -You are receiving this notification because you are watching the topic, "{TOPIC_TITLE}" at "{SITENAME}". This topic has received a reply since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. +You are receiving this notification because you are watching the topic, "{TOPIC_TITLE}" at "{SITENAME}". This topic has received a reply by {AUTHOR_NAME} since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. If you want to view the newest post made since your last visit, click the following link: {U_NEWEST_POST} From 15d2f294c664e11fb1f2bc84238067cf71a544e6 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 25 Mar 2012 20:56:00 -0400 Subject: [PATCH 0040/1142] [feature/prune-users] Cosmetic changes per bantu's review. PHPBB3-9622 --- phpBB/includes/acp/acp_prune.php | 5 +++-- phpBB/includes/functions_user.php | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/phpBB/includes/acp/acp_prune.php b/phpBB/includes/acp/acp_prune.php index b10eb292d2..145c0c3854 100644 --- a/phpBB/includes/acp/acp_prune.php +++ b/phpBB/includes/acp/acp_prune.php @@ -333,7 +333,8 @@ class acp_prune } $s_group_list = ''; - $sql = 'SELECT group_id, group_name FROM ' . GROUPS_TABLE . ' + $sql = 'SELECT group_id, group_name + FROM ' . GROUPS_TABLE . ' WHERE group_type <> ' . GROUP_SPECIAL . ' ORDER BY group_name ASC'; $result = $db->sql_query($sql); @@ -403,7 +404,7 @@ class acp_prune // so that our time range is a full day instead of 1 second if ($joined_after == $joined_before) { - $joined_after[2] += 1; //$joined_after[2]++; + $joined_after[2] += 1; } $joined_sql = ' AND user_regdate BETWEEN ' . gmmktime(0, 0, 0, (int) $joined_after[1], (int) $joined_after[2], (int) $joined_after[0]) . ' AND ' . gmmktime(0, 0, 0, (int) $joined_before[1], (int) $joined_before[2], (int) $joined_before[0]); diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index e9655ba736..c78949342b 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -365,8 +365,7 @@ function user_delete($mode, $user_ids, $retain_username = true) // Before we begin, we will remove the reports the user issued. $sql = 'SELECT r.post_id, p.topic_id FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . ' p - WHERE ' . $db->sql_in_set('r.user_id', $user_ids) . //r.user_id = ' . $user_id . ' - ' + WHERE ' . $db->sql_in_set('r.user_id', $user_ids) . ' AND p.post_id = r.post_id'; $result = $db->sql_query($sql); @@ -554,13 +553,13 @@ function user_delete($mode, $user_ids, $retain_username = true) // Change user_id to anonymous for this users triggered events $sql = 'UPDATE ' . LOG_TABLE . ' SET user_id = ' . ANONYMOUS . ' - WHERE ' . $user_id_sql; //user_id = ' . $user_id; + WHERE ' . $user_id_sql; $db->sql_query($sql); // Delete the user_id from the zebra table $sql = 'DELETE FROM ' . ZEBRA_TABLE . ' - WHERE ' . $user_id_sql . //user_id = ' . $user_id . ' - ' OR ' . $db->sql_in_set('zebra_id', $user_ids); + WHERE ' . $user_id_sql . ' + OR ' . $db->sql_in_set('zebra_id', $user_ids); $db->sql_query($sql); // Delete the user_id from the banlist From 8e2cbe39cdd7672638dbc0d6a0f65a7365db0d91 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 31 Mar 2012 04:06:52 +0200 Subject: [PATCH 0041/1142] [feature/dic] Convert common.php to Symfony2 DependencyInjection component PHPBB3-10739 --- phpBB/common.php | 43 +++++++++--------- phpBB/composer.json | 5 ++- phpBB/composer.lock | 16 ++++++- phpBB/config/parameters.yml | 11 +++++ phpBB/config/services.yml | 87 +++++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 24 deletions(-) create mode 100644 phpBB/config/parameters.yml create mode 100644 phpBB/config/services.yml diff --git a/phpBB/common.php b/phpBB/common.php index b3b8d7e4f7..aa6115fe1c 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -15,6 +15,9 @@ if (!defined('IN_PHPBB')) exit; } +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\EventDispatcher\EventDispatcher; require($phpbb_root_path . 'includes/startup.' . $phpEx); @@ -91,43 +94,41 @@ $phpbb_class_loader_ext->register(); $phpbb_class_loader = new phpbb_class_loader('phpbb_', $phpbb_root_path . 'includes/', ".$phpEx"); $phpbb_class_loader->register(); +$container = new ContainerBuilder(); +$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/config')); +$loader->load('parameters.yml'); +$loader->load('services.yml'); + +$container->setParameter('core.root_path', $phpbb_root_path); +$container->setParameter('core.php_ext', $phpEx); + // set up caching -$cache_factory = new phpbb_cache_factory($acm_type); -$cache = $cache_factory->get_service(); +$cache = $container->get('cache'); $phpbb_class_loader_ext->set_cache($cache->get_driver()); $phpbb_class_loader->set_cache($cache->get_driver()); // Instantiate some basic classes -$phpbb_dispatcher = new phpbb_event_dispatcher(); -$request = new phpbb_request(); -$user = new phpbb_user(); -$auth = new phpbb_auth(); -$db = new $sql_db(); +$phpbb_dispatcher = $container->get('dispatcher'); +$request = $container->get('request'); +$user = $container->get('user'); +$auth = $container->get('auth'); +$db = $container->get('dbal.conn'); // make sure request_var uses this request instance request_var('', 0, false, false, $request); // "dependency injection" for a function -// Connect to DB -$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, defined('PHPBB_DB_NEW_LINK') ? PHPBB_DB_NEW_LINK : false); - -// We do not need this any longer, unset for safety purposes -unset($dbpasswd); - // Grab global variables, re-cache if necessary -$config = new phpbb_config_db($db, $cache->get_driver(), CONFIG_TABLE); +$config = $container->get('config'); set_config(null, null, null, $config); set_config_count(null, null, null, $config); // load extensions -$phpbb_extension_manager = new phpbb_extension_manager($db, EXT_TABLE, $phpbb_root_path, ".$phpEx", $cache->get_driver()); +$phpbb_extension_manager = $container->get('ext.manager'); -// Initialize style -$phpbb_style_resource_locator = new phpbb_style_resource_locator(); -$phpbb_style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider()); -$template = new phpbb_style_template($phpbb_root_path, $phpEx, $config, $user, $phpbb_style_resource_locator, $phpbb_style_path_provider); -$style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $phpbb_style_resource_locator, $phpbb_style_path_provider, $template); +$template = $container->get('template'); +$style = $container->get('style'); -$phpbb_subscriber_loader = new phpbb_event_extension_subscriber_loader($phpbb_dispatcher, $phpbb_extension_manager); +$phpbb_subscriber_loader = $container->get('event.subscriber_loader'); $phpbb_subscriber_loader->load(); // Add own hook handler diff --git a/phpBB/composer.json b/phpBB/composer.json index 1059b97f84..56fb2454d1 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -1,5 +1,8 @@ { "require": { - "symfony/event-dispatcher": "2.0.*" + "symfony/config": "2.0.*", + "symfony/dependency-injection": "2.0.*", + "symfony/event-dispatcher": "2.0.*", + "symfony/yaml": "2.0.*" } } diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 062ad4b3aa..1707524da1 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -1,9 +1,21 @@ { - "hash": "9bada3748ec2933fe0864dcfafbcd671", + "hash": "b1e9c3bcfcee0c5630742abb3e49685f", "packages": [ + { + "package": "symfony/config", + "version": "v2.0.12" + }, + { + "package": "symfony/dependency-injection", + "version": "v2.0.12" + }, { "package": "symfony/event-dispatcher", - "version": "v2.0.10" + "version": "v2.0.12" + }, + { + "package": "symfony/yaml", + "version": "v2.0.12" } ], "aliases": [] diff --git a/phpBB/config/parameters.yml b/phpBB/config/parameters.yml new file mode 100644 index 0000000000..8a90c8e99d --- /dev/null +++ b/phpBB/config/parameters.yml @@ -0,0 +1,11 @@ +parameters: + cache.acm_type: file + dbal.driver: dbal_mysqli + dbal.dbhost: + dbal.dbuser: root + dbal.dbpasswd: + dbal.dbname: phpbb_dev + dbal.dbport: + dbal.new_link: false + tables.config: phpbb_config + tables.ext: phpbb_ext diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml new file mode 100644 index 0000000000..2bf8478f82 --- /dev/null +++ b/phpBB/config/services.yml @@ -0,0 +1,87 @@ +services: + cache_factory: + class: phpbb_cache_factory + arguments: + - %cache.acm_type% + + cache: + class: phpbb_cache_service + factory_service: cache_factory + factory_method: get_service + + cache.driver: + class: phpbb_cache_driver_interface + factory_service: cache + factory_method: get_driver + + dispatcher: + class: phpbb_event_dispatcher + + request: + class: phpbb_request + + user: + class: phpbb_user + + auth: + class: phpbb_auth + + dbal.conn: + class: %dbal.driver% + calls: + - [sql_connect, [%dbal.dbhost%, %dbal.dbuser%, %dbal.dbpasswd%, %dbal.dbname%, %dbal.dbport%, false, %dbal.new_link%]] + + config: + class: phpbb_config_db + arguments: + - @dbal.conn + - @cache.driver + - %tables.config% + + ext.manager: + class: phpbb_extension_manager + arguments: + - @dbal.conn + - %tables.ext% + - %core.root_path% + - .%core.php_ext% + - @cache.driver + + style.resource_locator: + class: phpbb_style_resource_locator + + style.path_provider_ext: + class: phpbb_style_extension_path_provider + arguments: + - @ext.manager + - @style.path_provider + + style.path_provider: + class: phpbb_style_path_provider + + template: + class: phpbb_style_template + arguments: + - %core.root_path% + - %core.php_ext% + - @config + - @user + - @style.resource_locator + - @style.path_provider_ext + + style: + class: phpbb_style + arguments: + - %core.root_path% + - %core.php_ext% + - @config + - @user + - @style.resource_locator + - @style.path_provider_ext + - @template + + event.subscriber_loader: + class: phpbb_event_extension_subscriber_loader + arguments: + - @dispatcher + - @ext.manager From b12f9a285546641415d9ea2dd9e3a3dba6527d1a Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 31 Mar 2012 20:21:26 +0200 Subject: [PATCH 0042/1142] [feature/dic] Remove cache factory, now handled by DIC PHPBB3-10739 --- phpBB/config/parameters.yml | 4 +-- phpBB/config/services.yml | 15 ++++-------- phpBB/includes/cache/factory.php | 42 -------------------------------- 3 files changed, 7 insertions(+), 54 deletions(-) delete mode 100644 phpBB/includes/cache/factory.php diff --git a/phpBB/config/parameters.yml b/phpBB/config/parameters.yml index 8a90c8e99d..da29ae8417 100644 --- a/phpBB/config/parameters.yml +++ b/phpBB/config/parameters.yml @@ -1,6 +1,6 @@ parameters: - cache.acm_type: file - dbal.driver: dbal_mysqli + cache.driver.class: phpbb_cache_driver_file + dbal.driver.class: dbal_mysqli dbal.dbhost: dbal.dbuser: root dbal.dbpasswd: diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 2bf8478f82..09eb993ca6 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -1,18 +1,13 @@ services: - cache_factory: - class: phpbb_cache_factory - arguments: - - %cache.acm_type% - cache: class: phpbb_cache_service - factory_service: cache_factory - factory_method: get_service + arguments: + - @cache.driver cache.driver: class: phpbb_cache_driver_interface - factory_service: cache - factory_method: get_driver + arguments: + - %cache.driver.class% dispatcher: class: phpbb_event_dispatcher @@ -27,7 +22,7 @@ services: class: phpbb_auth dbal.conn: - class: %dbal.driver% + class: %dbal.driver.class% calls: - [sql_connect, [%dbal.dbhost%, %dbal.dbuser%, %dbal.dbpasswd%, %dbal.dbname%, %dbal.dbport%, false, %dbal.new_link%]] diff --git a/phpBB/includes/cache/factory.php b/phpBB/includes/cache/factory.php deleted file mode 100644 index 01c4d0b901..0000000000 --- a/phpBB/includes/cache/factory.php +++ /dev/null @@ -1,42 +0,0 @@ -acm_type = $acm_type; - } - - public function get_driver() - { - $class_name = 'phpbb_cache_driver_' . $this->acm_type; - return new $class_name(); - } - - public function get_service() - { - $driver = $this->get_driver(); - $service = new phpbb_cache_service($driver); - return $service; - } -} From 776160a7e35a1f82325b7acf573906310791c573 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 31 Mar 2012 20:23:33 +0200 Subject: [PATCH 0043/1142] [feature/dic] Fetch cache driver explicitly PHPBB3-10739 --- phpBB/common.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/common.php b/phpBB/common.php index aa6115fe1c..591969df74 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -104,8 +104,8 @@ $container->setParameter('core.php_ext', $phpEx); // set up caching $cache = $container->get('cache'); -$phpbb_class_loader_ext->set_cache($cache->get_driver()); -$phpbb_class_loader->set_cache($cache->get_driver()); +$phpbb_class_loader_ext->set_cache($container->get('cache.driver')); +$phpbb_class_loader->set_cache($container->get('cache.driver')); // Instantiate some basic classes $phpbb_dispatcher = $container->get('dispatcher'); From bca600877cbd92246949366238d365bbc3039d5a Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 31 Mar 2012 20:27:46 +0200 Subject: [PATCH 0044/1142] [feature/dic] Move cron manager into DIC PHPBB3-10739 --- phpBB/common.php | 2 +- phpBB/config/services.yml | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/phpBB/common.php b/phpBB/common.php index 591969df74..0446b5c15e 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -142,5 +142,5 @@ foreach ($cache->obtain_hooks() as $hook) if (!$config['use_system_cron']) { - $cron = new phpbb_cron_manager(new phpbb_cron_task_provider($phpbb_extension_manager), $cache->get_driver()); + $cron = $container->get('cron.manager'); } diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 09eb993ca6..85a230de52 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -80,3 +80,14 @@ services: arguments: - @dispatcher - @ext.manager + + cron.task_provider: + class: phpbb_cron_task_provider + arguments: + - @ext.manager + + cron.manager: + class: phpbb_cron_manager + arguments: + - @cron.task_provider + - @cache.driver From 873630f04e6502cc69e6b208f596414f240bc923 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 31 Mar 2012 20:45:33 +0200 Subject: [PATCH 0045/1142] [feature/dic] Move class loader into DIC PHPBB3-10739 --- phpBB/common.php | 17 ++++++++--------- phpBB/config/services.yml | 21 ++++++++++++++++++--- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/phpBB/common.php b/phpBB/common.php index 0446b5c15e..117dc2051e 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -88,12 +88,6 @@ require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); // Set PHP error handler to ours set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); -// Setup class loader first -$phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', $phpbb_root_path . 'ext/', ".$phpEx"); -$phpbb_class_loader_ext->register(); -$phpbb_class_loader = new phpbb_class_loader('phpbb_', $phpbb_root_path . 'includes/', ".$phpEx"); -$phpbb_class_loader->register(); - $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/config')); $loader->load('parameters.yml'); @@ -102,6 +96,12 @@ $loader->load('services.yml'); $container->setParameter('core.root_path', $phpbb_root_path); $container->setParameter('core.php_ext', $phpEx); +// Setup class loader first +$phpbb_class_loader_ext = $container->get('class_loader.ext'); +$phpbb_class_loader_ext->register(); +$phpbb_class_loader = $container->get('class_loader'); +$phpbb_class_loader->register(); + // set up caching $cache = $container->get('cache'); $phpbb_class_loader_ext->set_cache($container->get('cache.driver')); @@ -124,13 +124,12 @@ set_config_count(null, null, null, $config); // load extensions $phpbb_extension_manager = $container->get('ext.manager'); +$phpbb_subscriber_loader = $container->get('event.subscriber_loader'); +$phpbb_subscriber_loader->load(); $template = $container->get('template'); $style = $container->get('style'); -$phpbb_subscriber_loader = $container->get('event.subscriber_loader'); -$phpbb_subscriber_loader->load(); - // Add own hook handler require($phpbb_root_path . 'includes/hooks/index.' . $phpEx); $phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('template', 'display'))); diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 85a230de52..ef459f4903 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -1,13 +1,28 @@ services: + class_loader: + class: phpbb_class_loader + arguments: + - phpbb_ + - %core.root_path%includes/ + - .%core.php_ext% + + class_loader.ext: + class: phpbb_class_loader + arguments: + - phpbb_ext_ + - %core.root_path%ext/ + - .%core.php_ext% + cache: class: phpbb_cache_service arguments: - @cache.driver cache.driver: - class: phpbb_cache_driver_interface - arguments: - - %cache.driver.class% + class: %cache.driver.class% + + cache.driver.install: + class: phpbb_cache_driver_file dispatcher: class: phpbb_event_dispatcher From a7f61b91b7feec80cd9d544502aef937f036ae7c Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 31 Mar 2012 20:45:58 +0200 Subject: [PATCH 0046/1142] [feature/dic] Use DIC in download/file and install/index PHPBB3-10739 --- phpBB/download/file.php | 39 ++++++++++++++++++++++----------------- phpBB/install/index.php | 24 ++++++++++++++++-------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/phpBB/download/file.php b/phpBB/download/file.php index c01b0789de..bc7042fbc8 100644 --- a/phpBB/download/file.php +++ b/phpBB/download/file.php @@ -14,7 +14,6 @@ define('IN_PHPBB', true); $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './../'; $phpEx = substr(strrchr(__FILE__, '.'), 1); - // Thank you sun. if (isset($_SERVER['CONTENT_TYPE'])) { @@ -45,20 +44,27 @@ if (isset($_GET['avatar'])) require($phpbb_root_path . 'includes/functions_download' . '.' . $phpEx); require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); - $phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', $phpbb_root_path . 'ext/', ".$phpEx"); + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); + $loader->load('parameters.yml'); + $loader->load('services.yml'); + + $container->setParameter('core.root_path', $phpbb_root_path); + $container->setParameter('core.php_ext', $phpEx); + + $phpbb_class_loader_ext = $container->get('class_loader.ext'); $phpbb_class_loader_ext->register(); - $phpbb_class_loader = new phpbb_class_loader('phpbb_', $phpbb_root_path . 'includes/', ".$phpEx"); + $phpbb_class_loader = $container->get('class_loader'); $phpbb_class_loader->register(); // set up caching - $cache_factory = new phpbb_cache_factory($acm_type); - $cache = $cache_factory->get_service(); - $phpbb_class_loader_ext->set_cache($cache->get_driver()); - $phpbb_class_loader->set_cache($cache->get_driver()); + $cache = $container->get('cache'); + $phpbb_class_loader_ext->set_cache($container->get('cache.driver')); + $phpbb_class_loader->set_cache($container->get('cache.driver')); - $phpbb_dispatcher = new phpbb_event_dispatcher(); - $request = new phpbb_request(); - $db = new $sql_db(); + $phpbb_dispatcher = $container->get('dispatcher'); + $request = $container->get('request'); + $db = $container->get('dbal.conn'); // Connect to DB if (!@$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, false)) @@ -69,19 +75,18 @@ if (isset($_GET['avatar'])) request_var('', 0, false, false, $request); - // worst-case default - $browser = strtolower($request->header('User-Agent', 'msie 6.0')); - - $config = new phpbb_config_db($db, $cache->get_driver(), CONFIG_TABLE); + $config = $container->get('config'); set_config(null, null, null, $config); set_config_count(null, null, null, $config); // load extensions - $phpbb_extension_manager = new phpbb_extension_manager($db, EXT_TABLE, $phpbb_root_path, ".$phpEx", $cache->get_driver()); - - $phpbb_subscriber_loader = new phpbb_event_extension_subscriber_loader($phpbb_dispatcher, $phpbb_extension_manager); + $phpbb_extension_manager = $container->get('ext.manager'); + $phpbb_subscriber_loader = $container->get('event.subscriber_loader'); $phpbb_subscriber_loader->load(); + // worst-case default + $browser = strtolower($request->header('User-Agent', 'msie 6.0')); + $filename = request_var('avatar', ''); $avatar_group = false; $exit = false; diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 13ab30a9fa..36c10c3ca6 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -79,19 +79,27 @@ include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); require($phpbb_root_path . 'includes/functions_install.' . $phpEx); -$phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', $phpbb_root_path . 'ext/', ".$phpEx"); +$container = new ContainerBuilder(); +$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); +$loader->load('parameters.yml'); +$loader->load('services.yml'); + +$container->setParameter('core.root_path', $phpbb_root_path); +$container->setParameter('core.php_ext', $phpEx); +$container->setAlias('cache.driver.install', 'cache.driver'); + +$phpbb_class_loader_ext = $container->get('class_loader.ext'); $phpbb_class_loader_ext->register(); -$phpbb_class_loader = new phpbb_class_loader('phpbb_', $phpbb_root_path . 'includes/', ".$phpEx"); +$phpbb_class_loader = $container->get('class_loader'); $phpbb_class_loader->register(); // set up caching -$cache_factory = new phpbb_cache_factory('file'); -$cache = $cache_factory->get_service(); -$phpbb_class_loader_ext->set_cache($cache->get_driver()); -$phpbb_class_loader->set_cache($cache->get_driver()); +$cache = $container->get('cache'); +$phpbb_class_loader_ext->set_cache($container->get('cache.driver')); +$phpbb_class_loader->set_cache($container->get('cache.driver')); -$phpbb_dispatcher = new phpbb_event_dispatcher(); -$request = new phpbb_request(); +$phpbb_dispatcher = $container->get('dispatcher'); +$request = $container->get('request'); // make sure request_var uses this request instance request_var('', 0, false, false, $request); // "dependency injection" for a function From dc9ccc432cd0b24cf762f8285d8a90f52b8efe5b Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 31 Mar 2012 21:20:58 +0200 Subject: [PATCH 0047/1142] [feature/dic] Make use of calls to cut down on boilerplate PHPBB3-10739 --- phpBB/common.php | 7 +------ phpBB/config/services.yml | 8 ++++++++ phpBB/download/file.php | 7 +------ phpBB/install/index.php | 6 +----- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/phpBB/common.php b/phpBB/common.php index 117dc2051e..51478662d7 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -97,15 +97,11 @@ $container->setParameter('core.root_path', $phpbb_root_path); $container->setParameter('core.php_ext', $phpEx); // Setup class loader first -$phpbb_class_loader_ext = $container->get('class_loader.ext'); -$phpbb_class_loader_ext->register(); $phpbb_class_loader = $container->get('class_loader'); -$phpbb_class_loader->register(); +$phpbb_class_loader_ext = $container->get('class_loader.ext'); // set up caching $cache = $container->get('cache'); -$phpbb_class_loader_ext->set_cache($container->get('cache.driver')); -$phpbb_class_loader->set_cache($container->get('cache.driver')); // Instantiate some basic classes $phpbb_dispatcher = $container->get('dispatcher'); @@ -125,7 +121,6 @@ set_config_count(null, null, null, $config); // load extensions $phpbb_extension_manager = $container->get('ext.manager'); $phpbb_subscriber_loader = $container->get('event.subscriber_loader'); -$phpbb_subscriber_loader->load(); $template = $container->get('template'); $style = $container->get('style'); diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index ef459f4903..6f2ed8c7c3 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -5,6 +5,9 @@ services: - phpbb_ - %core.root_path%includes/ - .%core.php_ext% + calls: + - [register, []] + - [set_cache, [@cache.driver]] class_loader.ext: class: phpbb_class_loader @@ -12,6 +15,9 @@ services: - phpbb_ext_ - %core.root_path%ext/ - .%core.php_ext% + calls: + - [register, []] + - [set_cache, [@cache.driver]] cache: class: phpbb_cache_service @@ -95,6 +101,8 @@ services: arguments: - @dispatcher - @ext.manager + calls: + - [load, []] cron.task_provider: class: phpbb_cron_task_provider diff --git a/phpBB/download/file.php b/phpBB/download/file.php index bc7042fbc8..eabb6edbbb 100644 --- a/phpBB/download/file.php +++ b/phpBB/download/file.php @@ -52,15 +52,11 @@ if (isset($_GET['avatar'])) $container->setParameter('core.root_path', $phpbb_root_path); $container->setParameter('core.php_ext', $phpEx); - $phpbb_class_loader_ext = $container->get('class_loader.ext'); - $phpbb_class_loader_ext->register(); $phpbb_class_loader = $container->get('class_loader'); - $phpbb_class_loader->register(); + $phpbb_class_loader_ext = $container->get('class_loader.ext'); // set up caching $cache = $container->get('cache'); - $phpbb_class_loader_ext->set_cache($container->get('cache.driver')); - $phpbb_class_loader->set_cache($container->get('cache.driver')); $phpbb_dispatcher = $container->get('dispatcher'); $request = $container->get('request'); @@ -82,7 +78,6 @@ if (isset($_GET['avatar'])) // load extensions $phpbb_extension_manager = $container->get('ext.manager'); $phpbb_subscriber_loader = $container->get('event.subscriber_loader'); - $phpbb_subscriber_loader->load(); // worst-case default $browser = strtolower($request->header('User-Agent', 'msie 6.0')); diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 36c10c3ca6..0d7a0a288c 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -88,15 +88,11 @@ $container->setParameter('core.root_path', $phpbb_root_path); $container->setParameter('core.php_ext', $phpEx); $container->setAlias('cache.driver.install', 'cache.driver'); -$phpbb_class_loader_ext = $container->get('class_loader.ext'); -$phpbb_class_loader_ext->register(); $phpbb_class_loader = $container->get('class_loader'); -$phpbb_class_loader->register(); +$phpbb_class_loader_ext = $container->get('class_loader.ext'); // set up caching $cache = $container->get('cache'); -$phpbb_class_loader_ext->set_cache($container->get('cache.driver')); -$phpbb_class_loader->set_cache($container->get('cache.driver')); $phpbb_dispatcher = $container->get('dispatcher'); $request = $container->get('request'); From 35c78c127b8fefe56512faaf83c47bee28908e0f Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sun, 1 Apr 2012 00:59:23 +0200 Subject: [PATCH 0048/1142] [feature/dic] Make table_config a DIC parameter PHPBB3-10739 --- phpBB/config/parameters.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phpBB/config/parameters.yml b/phpBB/config/parameters.yml index da29ae8417..3bedc9a135 100644 --- a/phpBB/config/parameters.yml +++ b/phpBB/config/parameters.yml @@ -1,4 +1,5 @@ parameters: + core.table_prefix: phpbb_ cache.driver.class: phpbb_cache_driver_file dbal.driver.class: dbal_mysqli dbal.dbhost: @@ -7,5 +8,5 @@ parameters: dbal.dbname: phpbb_dev dbal.dbport: dbal.new_link: false - tables.config: phpbb_config - tables.ext: phpbb_ext + tables.config: %core.table_prefix%config + tables.ext: %core.table_prefix%ext From e78fbfef9c6aac1349d18454a4292781d661795c Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sun, 1 Apr 2012 01:13:00 +0200 Subject: [PATCH 0049/1142] [feature/dic] Move cron.lock_db into DIC PHPBB3-10739 --- phpBB/config/services.yml | 7 +++++++ phpBB/cron.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 6f2ed8c7c3..6817d8f015 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -114,3 +114,10 @@ services: arguments: - @cron.task_provider - @cache.driver + + cron.lock_db: + class: phpbb_lock_db + arguments: + - cron_lock + - @config + - @dbal.conn diff --git a/phpBB/cron.php b/phpBB/cron.php index 36b771f1b7..df48c2dc4d 100644 --- a/phpBB/cron.php +++ b/phpBB/cron.php @@ -71,7 +71,7 @@ else output_image(); } -$cron_lock = new phpbb_lock_db('cron_lock', $config, $db); +$cron_lock = $container->get('cron.lock_db'); if ($cron_lock->acquire()) { if ($config['use_system_cron']) From 328d4f1820c372e6ae9ee6af60afb5e53a2f015e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adonais=20Romero=20Gonz=C3=A1lez?= Date: Thu, 5 Apr 2012 18:41:27 -0500 Subject: [PATCH 0050/1142] [ticket/10661] Added   to enumerated recipients (prosilver) Added missing   to enumerated recipients after IF statements to see if it is a group or not (to_recipient.IS_GROUP and bcc_recipient.ISGROUP), in posting_editor template in prosilver style. PHPBB3-10661 --- phpBB/styles/prosilver/template/posting_editor.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/styles/prosilver/template/posting_editor.html b/phpBB/styles/prosilver/template/posting_editor.html index 5f7fb8408e..fc98a95a17 100644 --- a/phpBB/styles/prosilver/template/posting_editor.html +++ b/phpBB/styles/prosilver/template/posting_editor.html @@ -17,7 +17,7 @@
- {to_recipient.NAME} {to_recipient.NAME_FULL}  + {to_recipient.NAME}{to_recipient.NAME_FULL}   
@@ -29,7 +29,7 @@
- {bcc_recipient.NAME}{bcc_recipient.NAME_FULL}  + {bcc_recipient.NAME}{bcc_recipient.NAME_FULL}   
@@ -50,7 +50,7 @@
- {to_recipient.NAME}{to_recipient.NAME_FULL}  + {to_recipient.NAME}{to_recipient.NAME_FULL}   
From 2e76620c8824da62f97cfdaee8f9b1014159fd7c Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Mon, 9 Apr 2012 00:22:55 +0200 Subject: [PATCH 0051/1142] [feature/dic] Rewrite cron system to use DIC PHPBB3-10739 --- phpBB/common.php | 37 +++++----- phpBB/config/cron_tasks.yml | 72 +++++++++++++++++++ phpBB/config/services.yml | 5 +- phpBB/download/file.php | 27 +++---- phpBB/includes/cron/manager.php | 36 ++-------- .../cron/task/core/prune_all_forums.php | 25 ++++--- phpBB/includes/cron/task/core/prune_forum.php | 44 +++++------- phpBB/includes/cron/task/core/queue.php | 18 +++-- phpBB/includes/cron/task/core/tidy_cache.php | 17 +++-- .../includes/cron/task/core/tidy_database.php | 15 ++-- phpBB/includes/cron/task/core/tidy_search.php | 24 ++++--- .../includes/cron/task/core/tidy_sessions.php | 14 ++-- .../includes/cron/task/core/tidy_warnings.php | 18 +++-- phpBB/includes/cron/task/provider.php | 45 ++++++------ phpBB/install/index.php | 21 +++--- phpBB/viewforum.php | 4 +- 16 files changed, 257 insertions(+), 165 deletions(-) create mode 100644 phpBB/config/cron_tasks.yml diff --git a/phpBB/common.php b/phpBB/common.php index 51478662d7..c9eb5d217b 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -88,42 +88,43 @@ require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); // Set PHP error handler to ours set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); -$container = new ContainerBuilder(); -$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/config')); +$phpbb_container = new ContainerBuilder(); +$loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__.'/config')); $loader->load('parameters.yml'); $loader->load('services.yml'); -$container->setParameter('core.root_path', $phpbb_root_path); -$container->setParameter('core.php_ext', $phpEx); +$phpbb_container->setParameter('core.root_path', $phpbb_root_path); +$phpbb_container->setParameter('core.php_ext', $phpEx); +$phpbb_container->set('container', $phpbb_container); // Setup class loader first -$phpbb_class_loader = $container->get('class_loader'); -$phpbb_class_loader_ext = $container->get('class_loader.ext'); +$phpbb_class_loader = $phpbb_container->get('class_loader'); +$phpbb_class_loader_ext = $phpbb_container->get('class_loader.ext'); // set up caching -$cache = $container->get('cache'); +$cache = $phpbb_container->get('cache'); // Instantiate some basic classes -$phpbb_dispatcher = $container->get('dispatcher'); -$request = $container->get('request'); -$user = $container->get('user'); -$auth = $container->get('auth'); -$db = $container->get('dbal.conn'); +$phpbb_dispatcher = $phpbb_container->get('dispatcher'); +$request = $phpbb_container->get('request'); +$user = $phpbb_container->get('user'); +$auth = $phpbb_container->get('auth'); +$db = $phpbb_container->get('dbal.conn'); // make sure request_var uses this request instance request_var('', 0, false, false, $request); // "dependency injection" for a function // Grab global variables, re-cache if necessary -$config = $container->get('config'); +$config = $phpbb_container->get('config'); set_config(null, null, null, $config); set_config_count(null, null, null, $config); // load extensions -$phpbb_extension_manager = $container->get('ext.manager'); -$phpbb_subscriber_loader = $container->get('event.subscriber_loader'); +$phpbb_extension_manager = $phpbb_container->get('ext.manager'); +$phpbb_subscriber_loader = $phpbb_container->get('event.subscriber_loader'); -$template = $container->get('template'); -$style = $container->get('style'); +$template = $phpbb_container->get('template'); +$style = $phpbb_container->get('style'); // Add own hook handler require($phpbb_root_path . 'includes/hooks/index.' . $phpEx); @@ -136,5 +137,5 @@ foreach ($cache->obtain_hooks() as $hook) if (!$config['use_system_cron']) { - $cron = $container->get('cron.manager'); + $cron = $phpbb_container->get('cron.manager'); } diff --git a/phpBB/config/cron_tasks.yml b/phpBB/config/cron_tasks.yml new file mode 100644 index 0000000000..18a198fa27 --- /dev/null +++ b/phpBB/config/cron_tasks.yml @@ -0,0 +1,72 @@ +services: + cron.task.core.prune_all_forums: + class: phpbb_cron_task_core_prune_all_forums + arguments: + - %core.root_path% + - %core.php_ext% + - @config + - @dbal.conn + tags: + - { name: cron.task } + + cron.task.core.prune_forum: + class: phpbb_cron_task_core_prune_forum + arguments: + - %core.root_path% + - %core.php_ext% + - @config + - @dbal.conn + tags: + - { name: cron.task } + + cron.task.core.queue: + class: phpbb_cron_task_core_queue + arguments: + - %core.root_path% + - %core.php_ext% + - @config + tags: + - { name: cron.task } + + cron.task.core.tidy_cache: + class: phpbb_cron_task_core_tidy_cache + arguments: + - @config + - @cache.driver + tags: + - { name: cron.task } + + cron.task.core.tidy_database: + class: phpbb_cron_task_core_tidy_database + arguments: + - %core.root_path% + - %core.php_ext% + - @config + tags: + - { name: cron.task } + + cron.task.core.tidy_search: + class: phpbb_cron_task_core_tidy_search + arguments: + - %core.root_path% + - %core.php_ext% + - @config + tags: + - { name: cron.task } + + cron.task.core.tidy_sessions: + class: phpbb_cron_task_core_tidy_sessions + arguments: + - @config + - @user + tags: + - { name: cron.task } + + cron.task.core.tidy_warnings: + class: phpbb_cron_task_core_tidy_warnings + arguments: + - %core.root_path% + - %core.php_ext% + - @config + tags: + - { name: cron.task } diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 6817d8f015..f6e92112f7 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -1,3 +1,6 @@ +imports: + - { resource: cron_tasks.yml } + services: class_loader: class: phpbb_class_loader @@ -107,7 +110,7 @@ services: cron.task_provider: class: phpbb_cron_task_provider arguments: - - @ext.manager + - @container cron.manager: class: phpbb_cron_manager diff --git a/phpBB/download/file.php b/phpBB/download/file.php index eabb6edbbb..7213e3ac3f 100644 --- a/phpBB/download/file.php +++ b/phpBB/download/file.php @@ -44,23 +44,24 @@ if (isset($_GET['avatar'])) require($phpbb_root_path . 'includes/functions_download' . '.' . $phpEx); require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); - $container = new ContainerBuilder(); - $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); + $phpbb_container = new ContainerBuilder(); + $loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__.'/../config')); $loader->load('parameters.yml'); $loader->load('services.yml'); - $container->setParameter('core.root_path', $phpbb_root_path); - $container->setParameter('core.php_ext', $phpEx); + $phpbb_container->setParameter('core.root_path', $phpbb_root_path); + $phpbb_container->setParameter('core.php_ext', $phpEx); + $phpbb_container->set('container', $phpbb_container); - $phpbb_class_loader = $container->get('class_loader'); - $phpbb_class_loader_ext = $container->get('class_loader.ext'); + $phpbb_class_loader = $phpbb_container->get('class_loader'); + $phpbb_class_loader_ext = $phpbb_container->get('class_loader.ext'); // set up caching - $cache = $container->get('cache'); + $cache = $phpbb_container->get('cache'); - $phpbb_dispatcher = $container->get('dispatcher'); - $request = $container->get('request'); - $db = $container->get('dbal.conn'); + $phpbb_dispatcher = $phpbb_container->get('dispatcher'); + $request = $phpbb_container->get('request'); + $db = $phpbb_container->get('dbal.conn'); // Connect to DB if (!@$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, false)) @@ -71,13 +72,13 @@ if (isset($_GET['avatar'])) request_var('', 0, false, false, $request); - $config = $container->get('config'); + $config = $phpbb_container->get('config'); set_config(null, null, null, $config); set_config_count(null, null, null, $config); // load extensions - $phpbb_extension_manager = $container->get('ext.manager'); - $phpbb_subscriber_loader = $container->get('event.subscriber_loader'); + $phpbb_extension_manager = $phpbb_container->get('ext.manager'); + $phpbb_subscriber_loader = $phpbb_container->get('event.subscriber_loader'); // worst-case default $browser = strtolower($request->header('User-Agent', 'msie 6.0')); diff --git a/phpBB/includes/cron/manager.php b/phpBB/includes/cron/manager.php index 7a78a1b054..5abbc987dd 100644 --- a/phpBB/includes/cron/manager.php +++ b/phpBB/includes/cron/manager.php @@ -35,26 +35,25 @@ class phpbb_cron_manager /** * Constructor. Loads all available tasks. * - * @param array|Traversable $task_names Provides an iterable set of task names + * @param array|Traversable $tasks Provides an iterable set of task names */ - public function __construct($task_names) + public function __construct($tasks) { - $this->load_tasks($task_names); + $this->load_tasks($tasks); } /** * Loads tasks given by name, wraps them * and puts them into $this->tasks. * - * @param array|Traversable $task_names Array of strings + * @param array|Traversable $tasks Array of instances of phpbb_cron_task * * @return void */ - public function load_tasks($task_names) + public function load_tasks($tasks) { - foreach ($task_names as $task_name) + foreach ($tasks as $task) { - $task = new $task_name(); $wrapper = new phpbb_cron_task_wrapper($task); $this->tasks[] = $wrapper; } @@ -120,27 +119,4 @@ class phpbb_cron_manager } return null; } - - /** - * Creates an instance of parametrized cron task $name with args $args. - * The constructed task is wrapped with cron task wrapper before being returned. - * - * @param string $name The task name, which is the same as cron task class name. - * @param array $args Will be passed to the task class's constructor. - * - * @return phpbb_cron_task_wrapper|null - */ - public function instantiate_task($name, array $args) - { - $task = $this->find_task($name); - if ($task) - { - // task here is actually an instance of cron task wrapper - $class = $task->get_name(); - $task = new $class($args); - // need to wrap the new task too - $task = new phpbb_cron_task_wrapper($task); - } - return $task; - } } diff --git a/phpBB/includes/cron/task/core/prune_all_forums.php b/phpBB/includes/cron/task/core/prune_all_forums.php index 15b93a9ca6..f3a179d81b 100644 --- a/phpBB/includes/cron/task/core/prune_all_forums.php +++ b/phpBB/includes/cron/task/core/prune_all_forums.php @@ -26,6 +26,16 @@ if (!defined('IN_PHPBB')) */ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base { + private $phpbb_root_path, $phpEx, $config, $db; + + public function __construct($phpbb_root_path, $phpEx, phpbb_config $config, dbal $db) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->phpEx = $phpEx; + $this->config = $config; + $this->db = $db; + } + /** * Runs this cron task. * @@ -33,19 +43,17 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base */ public function run() { - global $phpbb_root_path, $phpEx, $db; - if (!function_exists('auto_prune')) { - include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->phpEx); } $sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq FROM ' . FORUMS_TABLE . " - WHERE enable_prune = 1 + WHERE enable_prune = 1 AND prune_next < " . time(); - $result = $db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) { if ($row['prune_days']) { @@ -57,7 +65,7 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base auto_prune($row['forum_id'], 'viewed', $row['forum_flags'], $row['prune_viewed'], $row['prune_freq']); } } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); } /** @@ -69,7 +77,6 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base */ public function is_runnable() { - global $config; - return (bool) $config['use_system_cron']; + return (bool) $this->config['use_system_cron']; } } diff --git a/phpBB/includes/cron/task/core/prune_forum.php b/phpBB/includes/cron/task/core/prune_forum.php index 7686fd4281..1f717904ef 100644 --- a/phpBB/includes/cron/task/core/prune_forum.php +++ b/phpBB/includes/cron/task/core/prune_forum.php @@ -26,31 +26,25 @@ if (!defined('IN_PHPBB')) */ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements phpbb_cron_task_parametrized { + private $phpbb_root_path, $phpEx, $config, $db; private $forum_data; + public function __construct($phpbb_root_path, $phpEx, phpbb_config $config, dbal $db) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->phpEx = $phpEx; + $this->config = $config; + $this->db = $db; + } + /** - * Constructor. - * - * If $forum_data is given, it is assumed to contain necessary information - * about a single forum that is to be pruned. - * - * If $forum_data is not given, forum id will be retrieved via request_var - * and a database query will be performed to load the necessary information - * about the forum. + * Manually set forum data. * * @param array $forum_data Information about a forum to be pruned. */ - public function __construct($forum_data = null) + public function set_forum_data($forum_data) { - global $db; - if ($forum_data) - { - $this->forum_data = $forum_data; - } - else - { - $this->forum_data = null; - } + $this->forum_data = $forum_data; } /** @@ -60,10 +54,9 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p */ public function run() { - global $phpbb_root_path, $phpEx; if (!function_exists('auto_prune')) { - include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->phpEx); } if ($this->forum_data['prune_days']) @@ -90,8 +83,7 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p */ public function is_runnable() { - global $config; - return !$config['use_system_cron'] && $this->forum_data; + return !$this->config['use_system_cron'] && $this->forum_data; } /** @@ -130,8 +122,6 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p */ public function parse_parameters(phpbb_request_interface $request) { - global $db; - $this->forum_data = null; if ($request->is_set('f')) { @@ -140,9 +130,9 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p $sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq FROM ' . FORUMS_TABLE . " WHERE forum_id = $forum_id"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); if ($row) { diff --git a/phpBB/includes/cron/task/core/queue.php b/phpBB/includes/cron/task/core/queue.php index 1c72eec7c7..70db94f31d 100644 --- a/phpBB/includes/cron/task/core/queue.php +++ b/phpBB/includes/cron/task/core/queue.php @@ -22,6 +22,15 @@ if (!defined('IN_PHPBB')) */ class phpbb_cron_task_core_queue extends phpbb_cron_task_base { + private $phpbb_root_path, $phpEx, $config; + + public function __construct($phpbb_root_path, $phpEx, phpbb_config $config) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->phpEx = $phpEx; + $this->config = $config; + } + /** * Runs this cron task. * @@ -29,10 +38,9 @@ class phpbb_cron_task_core_queue extends phpbb_cron_task_base */ public function run() { - global $phpbb_root_path, $phpEx; if (!class_exists('queue')) { - include($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->phpEx); } $queue = new queue(); $queue->process(); @@ -47,8 +55,7 @@ class phpbb_cron_task_core_queue extends phpbb_cron_task_base */ public function is_runnable() { - global $phpbb_root_path, $phpEx; - return file_exists($phpbb_root_path . 'cache/queue.' . $phpEx); + return file_exists($this->phpbb_root_path . 'cache/queue.' . $this->phpEx); } /** @@ -61,7 +68,6 @@ class phpbb_cron_task_core_queue extends phpbb_cron_task_base */ public function should_run() { - global $config; - return $config['last_queue_run'] < time() - $config['queue_interval_config']; + return $this->config['last_queue_run'] < time() - $this->config['queue_interval_config']; } } diff --git a/phpBB/includes/cron/task/core/tidy_cache.php b/phpBB/includes/cron/task/core/tidy_cache.php index c9dc0bd9ae..530f25dacb 100644 --- a/phpBB/includes/cron/task/core/tidy_cache.php +++ b/phpBB/includes/cron/task/core/tidy_cache.php @@ -22,6 +22,14 @@ if (!defined('IN_PHPBB')) */ class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base { + private $config, $cache; + + public function __construct(phpbb_config $config, phpbb_cache_driver_interface $cache) + { + $this->config = $config; + $this->cache = $cache; + } + /** * Runs this cron task. * @@ -29,8 +37,7 @@ class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base */ public function run() { - global $cache; - $cache->tidy(); + $this->cache->tidy(); } /** @@ -43,8 +50,7 @@ class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base */ public function is_runnable() { - global $cache; - return method_exists($cache, 'tidy'); + return method_exists($this->cache, 'tidy'); } /** @@ -58,7 +64,6 @@ class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base */ public function should_run() { - global $config; - return $config['cache_last_gc'] < time() - $config['cache_gc']; + return $this->config['cache_last_gc'] < time() - $this->config['cache_gc']; } } diff --git a/phpBB/includes/cron/task/core/tidy_database.php b/phpBB/includes/cron/task/core/tidy_database.php index 80a1901b1e..910e27cf20 100644 --- a/phpBB/includes/cron/task/core/tidy_database.php +++ b/phpBB/includes/cron/task/core/tidy_database.php @@ -22,6 +22,15 @@ if (!defined('IN_PHPBB')) */ class phpbb_cron_task_core_tidy_database extends phpbb_cron_task_base { + private $phpbb_root_path, $phpEx, $config; + + public function __construct($phpbb_root_path, $phpEx, phpbb_config $config) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->phpEx = $phpEx; + $this->config = $config; + } + /** * Runs this cron task. * @@ -29,10 +38,9 @@ class phpbb_cron_task_core_tidy_database extends phpbb_cron_task_base */ public function run() { - global $phpbb_root_path, $phpEx; if (!function_exists('tidy_database')) { - include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->phpEx); } tidy_database(); } @@ -48,7 +56,6 @@ class phpbb_cron_task_core_tidy_database extends phpbb_cron_task_base */ public function should_run() { - global $config; - return $config['database_last_gc'] < time() - $config['database_gc']; + return $this->config['database_last_gc'] < time() - $this->config['database_gc']; } } diff --git a/phpBB/includes/cron/task/core/tidy_search.php b/phpBB/includes/cron/task/core/tidy_search.php index 8a0b1b690a..1b8a0b8151 100644 --- a/phpBB/includes/cron/task/core/tidy_search.php +++ b/phpBB/includes/cron/task/core/tidy_search.php @@ -24,6 +24,15 @@ if (!defined('IN_PHPBB')) */ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base { + private $phpbb_root_path, $phpEx, $config; + + public function __construct($phpbb_root_path, $phpEx, phpbb_config $config) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->phpEx = $phpEx; + $this->config = $config; + } + /** * Runs this cron task. * @@ -31,14 +40,12 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base */ public function run() { - global $phpbb_root_path, $phpEx, $config, $error; - // Select the search method - $search_type = basename($config['search_type']); + $search_type = basename($this->config['search_type']); if (!class_exists($search_type)) { - include("{$phpbb_root_path}includes/search/$search_type.$phpEx"); + include($this->phpbb_root_path . "includes/search/$search_type." . $this->phpEx); } // We do some additional checks in the module to ensure it can actually be utilised @@ -62,12 +69,10 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base */ public function is_runnable() { - global $phpbb_root_path, $phpEx, $config; - // Select the search method - $search_type = basename($config['search_type']); + $search_type = basename($this->config['search_type']); - return file_exists($phpbb_root_path . 'includes/search/' . $search_type . '.' . $phpEx); + return file_exists($this->phpbb_root_path . 'includes/search/' . $search_type . '.' . $this->phpEx); } /** @@ -81,7 +86,6 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base */ public function should_run() { - global $config; - return $config['search_last_gc'] < time() - $config['search_gc']; + return $this->config['search_last_gc'] < time() - $this->config['search_gc']; } } diff --git a/phpBB/includes/cron/task/core/tidy_sessions.php b/phpBB/includes/cron/task/core/tidy_sessions.php index ae7bb242b8..b409c93868 100644 --- a/phpBB/includes/cron/task/core/tidy_sessions.php +++ b/phpBB/includes/cron/task/core/tidy_sessions.php @@ -22,6 +22,14 @@ if (!defined('IN_PHPBB')) */ class phpbb_cron_task_core_tidy_sessions extends phpbb_cron_task_base { + private $config, $user; + + public function __construct(phpbb_config $config, phpbb_user $user) + { + $this->config = $config; + $this->user = $user; + } + /** * Runs this cron task. * @@ -29,8 +37,7 @@ class phpbb_cron_task_core_tidy_sessions extends phpbb_cron_task_base */ public function run() { - global $user; - $user->session_gc(); + $this->user->session_gc(); } /** @@ -44,7 +51,6 @@ class phpbb_cron_task_core_tidy_sessions extends phpbb_cron_task_base */ public function should_run() { - global $config; - return $config['session_last_gc'] < time() - $config['session_gc']; + return $this->config['session_last_gc'] < time() - $this->config['session_gc']; } } diff --git a/phpBB/includes/cron/task/core/tidy_warnings.php b/phpBB/includes/cron/task/core/tidy_warnings.php index e1434e7087..3b87a300d0 100644 --- a/phpBB/includes/cron/task/core/tidy_warnings.php +++ b/phpBB/includes/cron/task/core/tidy_warnings.php @@ -24,6 +24,15 @@ if (!defined('IN_PHPBB')) */ class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base { + private $phpbb_root_path, $phpEx, $config; + + public function __construct($phpbb_root_path, $phpEx, phpbb_config $config) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->phpEx = $phpEx; + $this->config = $config; + } + /** * Runs this cron task. * @@ -31,10 +40,9 @@ class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base */ public function run() { - global $phpbb_root_path, $phpEx; if (!function_exists('tidy_warnings')) { - include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->phpEx); } tidy_warnings(); } @@ -48,8 +56,7 @@ class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base */ public function is_runnable() { - global $config; - return (bool) $config['warnings_expire_days']; + return (bool) $this->config['warnings_expire_days']; } /** @@ -63,7 +70,6 @@ class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base */ public function should_run() { - global $config; - return $config['warnings_last_gc'] < time() - $config['warnings_gc']; + return $this->config['warnings_last_gc'] < time() - $this->config['warnings_gc']; } } diff --git a/phpBB/includes/cron/task/provider.php b/phpBB/includes/cron/task/provider.php index 1482051699..acc3d455ec 100644 --- a/phpBB/includes/cron/task/provider.php +++ b/phpBB/includes/cron/task/provider.php @@ -15,6 +15,8 @@ if (!defined('IN_PHPBB')) exit; } +use Symfony\Component\DependencyInjection\Container; + /** * Provides cron manager with tasks * @@ -22,27 +24,30 @@ if (!defined('IN_PHPBB')) * * @package phpBB3 */ -class phpbb_cron_task_provider extends phpbb_extension_provider +class phpbb_cron_task_provider implements IteratorAggregate { - /** - * Finds cron task names using the extension manager. - * - * All PHP files in includes/cron/task/core/ are considered tasks. Tasks - * in extensions have to be located in a directory called cron or a subdir - * of a directory called cron. The class and filename must end in a _task - * suffix. Additionally all PHP files in includes/cron/task/core/ are - * tasks. - * - * @return array List of task names - */ - protected function find() - { - $finder = $this->extension_manager->get_finder(); + private $container; - return $finder - ->extension_suffix('_task') - ->extension_directory('/cron') - ->core_path('includes/cron/task/core/') - ->get_classes(); + public function __construct(Container $container) + { + $this->container = $container; + } + + /** + * Retrieve an iterator over all items + * + * @return ArrayIterator An iterator for the array of cron tasks + */ + public function getIterator() + { + $definitions = $this->container->findTaggedServiceIds('cron.task'); + + $tasks = array(); + foreach ($definitions as $name => $definition) + { + $tasks[] = $this->container->get($name); + } + + return new ArrayIterator($tasks); } } diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 0d7a0a288c..dd84e2d519 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -79,23 +79,24 @@ include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); require($phpbb_root_path . 'includes/functions_install.' . $phpEx); -$container = new ContainerBuilder(); -$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); +$phpbb_container = new ContainerBuilder(); +$loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__.'/../config')); $loader->load('parameters.yml'); $loader->load('services.yml'); -$container->setParameter('core.root_path', $phpbb_root_path); -$container->setParameter('core.php_ext', $phpEx); -$container->setAlias('cache.driver.install', 'cache.driver'); +$phpbb_container->setParameter('core.root_path', $phpbb_root_path); +$phpbb_container->setParameter('core.php_ext', $phpEx); +$phpbb_container->setAlias('cache.driver.install', 'cache.driver'); +$phpbb_container->set('container', $phpbb_container); -$phpbb_class_loader = $container->get('class_loader'); -$phpbb_class_loader_ext = $container->get('class_loader.ext'); +$phpbb_class_loader = $phpbb_container->get('class_loader'); +$phpbb_class_loader_ext = $phpbb_container->get('class_loader.ext'); // set up caching -$cache = $container->get('cache'); +$cache = $phpbb_container->get('cache'); -$phpbb_dispatcher = $container->get('dispatcher'); -$request = $container->get('request'); +$phpbb_dispatcher = $phpbb_container->get('dispatcher'); +$request = $phpbb_container->get('request'); // make sure request_var uses this request instance request_var('', 0, false, false, $request); // "dependency injection" for a function diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 2d91581cf4..16342ba1b3 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -193,7 +193,9 @@ if ($forum_data['forum_topics_per_page']) // Do the forum Prune thang - cron type job ... if (!$config['use_system_cron']) { - $task = $cron->instantiate_task('cron_task_core_prune_forum', $forum_data); + $task = $container->get('cron.task.core.prune_forum'); + $task = new phpbb_cron_task_wrapper($task); + $task->set_forum_data($forum_data); if ($task && $task->is_ready()) { $url = $task->get_url(); From aa0c995ed9cdafa2dfaca23b54d5b4cf6323f794 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Mon, 9 Apr 2012 13:15:27 +0200 Subject: [PATCH 0052/1142] [feature/dic] Protect config directory via .htaccess PHPBB3-10739 --- phpBB/config/.htaccess | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 phpBB/config/.htaccess diff --git a/phpBB/config/.htaccess b/phpBB/config/.htaccess new file mode 100644 index 0000000000..aa5afc1640 --- /dev/null +++ b/phpBB/config/.htaccess @@ -0,0 +1,4 @@ + + Order Allow,Deny + Deny from All + \ No newline at end of file From 3896ee953fbdb45d0485c0b5dcdfca47409a22ed Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Mon, 9 Apr 2012 14:34:35 +0200 Subject: [PATCH 0053/1142] [feature/dic] Give all cron tasks a name, change some manager usage PHPBB3-10739 --- phpBB/config/services.yml | 3 ++- phpBB/cron.php | 2 +- phpBB/includes/cron/manager.php | 15 ++++++++++++--- phpBB/includes/cron/task/base.php | 22 ++++++++++++++++++++++ phpBB/includes/cron/task/provider.php | 7 ++++++- phpBB/includes/cron/task/task.php | 7 +++++++ phpBB/includes/cron/task/wrapper.php | 20 ++++++-------------- phpBB/viewforum.php | 8 +++++--- 8 files changed, 61 insertions(+), 23 deletions(-) diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index f6e92112f7..94d641ab8b 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -116,7 +116,8 @@ services: class: phpbb_cron_manager arguments: - @cron.task_provider - - @cache.driver + - %core.root_path% + - %core.php_ext% cron.lock_db: class: phpbb_lock_db diff --git a/phpBB/cron.php b/phpBB/cron.php index df48c2dc4d..be328db4de 100644 --- a/phpBB/cron.php +++ b/phpBB/cron.php @@ -61,7 +61,7 @@ function do_cron($cron_lock, $run_tasks) if ($config['use_system_cron']) { - $cron = new phpbb_cron_manager(new phpbb_cron_task_provider($phpbb_extension_manager), $cache->get_driver()); + $cron = $container->get('cron.manager'); } else { diff --git a/phpBB/includes/cron/manager.php b/phpBB/includes/cron/manager.php index 5abbc987dd..5ea909eb2c 100644 --- a/phpBB/includes/cron/manager.php +++ b/phpBB/includes/cron/manager.php @@ -32,13 +32,18 @@ class phpbb_cron_manager */ protected $tasks = array(); + protected $phpbb_root_path, $phpEx; + /** * Constructor. Loads all available tasks. * * @param array|Traversable $tasks Provides an iterable set of task names */ - public function __construct($tasks) + public function __construct($tasks, $phpbb_root_path, $phpEx) { + $this->phpbb_root_path = $phpbb_root_path; + $this->phpEx = $phpEx; + $this->load_tasks($tasks); } @@ -54,8 +59,7 @@ class phpbb_cron_manager { foreach ($tasks as $task) { - $wrapper = new phpbb_cron_task_wrapper($task); - $this->tasks[] = $wrapper; + $this->tasks[] = $this->wrap_task($task); } } @@ -119,4 +123,9 @@ class phpbb_cron_manager } return null; } + + public function wrap_task(phpbb_cron_task $task) + { + return new phpbb_cron_task_wrapper($task, $this->phpbb_root_path, $this->phpEx); + } } diff --git a/phpBB/includes/cron/task/base.php b/phpBB/includes/cron/task/base.php index c05fb9a87c..94a2f267b4 100644 --- a/phpBB/includes/cron/task/base.php +++ b/phpBB/includes/cron/task/base.php @@ -28,6 +28,28 @@ if (!defined('IN_PHPBB')) */ abstract class phpbb_cron_task_base implements phpbb_cron_task { + private $name; + + /** + * Returns the name of the task. + * + * @return string Name of wrapped task. + */ + public function get_name() + { + return $this->name; + } + + /** + * Sets the name of the task. + * + * @param string $name The task name + */ + public function set_name($name) + { + $this->name = $name; + } + /** * Returns whether this cron task can run, given current board configuration. * diff --git a/phpBB/includes/cron/task/provider.php b/phpBB/includes/cron/task/provider.php index acc3d455ec..9994d707f2 100644 --- a/phpBB/includes/cron/task/provider.php +++ b/phpBB/includes/cron/task/provider.php @@ -45,7 +45,12 @@ class phpbb_cron_task_provider implements IteratorAggregate $tasks = array(); foreach ($definitions as $name => $definition) { - $tasks[] = $this->container->get($name); + $task = $this->container->get($name); + if ($task instanceof phpbb_cron_task_base) { + $task->set_name($name); + } + + $tasks[] = $task; } return new ArrayIterator($tasks); diff --git a/phpBB/includes/cron/task/task.php b/phpBB/includes/cron/task/task.php index 2f2a9e51f9..7b08fed413 100644 --- a/phpBB/includes/cron/task/task.php +++ b/phpBB/includes/cron/task/task.php @@ -21,6 +21,13 @@ if (!defined('IN_PHPBB')) */ interface phpbb_cron_task { + /** + * Returns the name of the task. + * + * @return string Name of wrapped task. + */ + public function get_name(); + /** * Runs this cron task. * diff --git a/phpBB/includes/cron/task/wrapper.php b/phpBB/includes/cron/task/wrapper.php index 66c45189e5..75b7fbdaa3 100644 --- a/phpBB/includes/cron/task/wrapper.php +++ b/phpBB/includes/cron/task/wrapper.php @@ -23,6 +23,8 @@ if (!defined('IN_PHPBB')) */ class phpbb_cron_task_wrapper { + private $task, $phpbb_root_path, $phpEx; + /** * Constructor. * @@ -30,9 +32,11 @@ class phpbb_cron_task_wrapper * * @param phpbb_cron_task $task The cron task to wrap. */ - public function __construct(phpbb_cron_task $task) + public function __construct(phpbb_cron_task $task, $phpbb_root_path, $phpEx) { $this->task = $task; + $this->phpbb_root_path = $phpbb_root_path; + $this->phpEx = $phpEx; } /** @@ -61,16 +65,6 @@ class phpbb_cron_task_wrapper return $this->task->is_runnable() && $this->task->should_run(); } - /** - * Returns the name of wrapped task. It is the same as the wrapped class's class name. - * - * @return string Class name of wrapped task. - */ - public function get_name() - { - return get_class($this->task); - } - /** * Returns a url through which this task may be invoked via web. * @@ -82,8 +76,6 @@ class phpbb_cron_task_wrapper */ public function get_url() { - global $phpbb_root_path, $phpEx; - $name = $this->get_name(); if ($this->is_parametrized()) { @@ -98,7 +90,7 @@ class phpbb_cron_task_wrapper { $extra = ''; } - $url = append_sid($phpbb_root_path . 'cron.' . $phpEx, 'cron_type=' . $name . $extra); + $url = append_sid($this->phpbb_root_path . 'cron.' . $this->phpEx, 'cron_type=' . $name . $extra); return $url; } diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 16342ba1b3..25c8f5aa6d 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -193,10 +193,12 @@ if ($forum_data['forum_topics_per_page']) // Do the forum Prune thang - cron type job ... if (!$config['use_system_cron']) { - $task = $container->get('cron.task.core.prune_forum'); - $task = new phpbb_cron_task_wrapper($task); + $cron = $container->get('cron.manager'); + + $task = $cron->find_task('cron.task.core.prune_forum'); $task->set_forum_data($forum_data); - if ($task && $task->is_ready()) + + if ($task->is_ready()) { $url = $task->get_url(); $template->assign_var('RUN_CRON_TASK', 'cron'); From 0a5348a1376fafb487884086a70069bea8c5640b Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Mon, 9 Apr 2012 14:37:04 +0200 Subject: [PATCH 0054/1142] [feature/dic] Rename occurances of $container to $phpbb_container PHPBB3-10739 --- phpBB/cron.php | 4 ++-- phpBB/viewforum.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/cron.php b/phpBB/cron.php index be328db4de..95d2f8f9b6 100644 --- a/phpBB/cron.php +++ b/phpBB/cron.php @@ -61,7 +61,7 @@ function do_cron($cron_lock, $run_tasks) if ($config['use_system_cron']) { - $cron = $container->get('cron.manager'); + $cron = $phpbb_container->get('cron.manager'); } else { @@ -71,7 +71,7 @@ else output_image(); } -$cron_lock = $container->get('cron.lock_db'); +$cron_lock = $phpbb_container->get('cron.lock_db'); if ($cron_lock->acquire()) { if ($config['use_system_cron']) diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 25c8f5aa6d..76d5c8ccff 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -193,7 +193,7 @@ if ($forum_data['forum_topics_per_page']) // Do the forum Prune thang - cron type job ... if (!$config['use_system_cron']) { - $cron = $container->get('cron.manager'); + $cron = $phpbb_container->get('cron.manager'); $task = $cron->find_task('cron.task.core.prune_forum'); $task->set_forum_data($forum_data); From 3ebe89cb7eff57c3ffdbe0b7dcd9cdc35b48d26b Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Mon, 9 Apr 2012 15:05:28 +0200 Subject: [PATCH 0055/1142] [feature/dic] Fix test suite for dic-powered cron PHPBB3-10739 --- phpBB/includes/cron/task/provider.php | 4 +- tests/cron/ext/testext/cron/dummy_task.php | 5 +++ .../includes/cron/task/core/dummy_task.php | 5 +++ .../cron/task/core/second_dummy_task.php | 5 +++ tests/cron/manager_test.php | 32 +++++++-------- tests/cron/task_provider_test.php | 41 ++++++++++++------- tests/cron/tasks/simple_not_runnable.php | 5 +++ tests/cron/tasks/simple_ready.php | 5 +++ tests/cron/tasks/simple_should_not_run.php | 5 +++ 9 files changed, 74 insertions(+), 33 deletions(-) diff --git a/phpBB/includes/cron/task/provider.php b/phpBB/includes/cron/task/provider.php index 9994d707f2..6adac77eb8 100644 --- a/phpBB/includes/cron/task/provider.php +++ b/phpBB/includes/cron/task/provider.php @@ -15,7 +15,7 @@ if (!defined('IN_PHPBB')) exit; } -use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\TaggedContainerInterface; /** * Provides cron manager with tasks @@ -28,7 +28,7 @@ class phpbb_cron_task_provider implements IteratorAggregate { private $container; - public function __construct(Container $container) + public function __construct(TaggedContainerInterface $container) { $this->container = $container; } diff --git a/tests/cron/ext/testext/cron/dummy_task.php b/tests/cron/ext/testext/cron/dummy_task.php index 996f5b39cf..a31806c1b1 100644 --- a/tests/cron/ext/testext/cron/dummy_task.php +++ b/tests/cron/ext/testext/cron/dummy_task.php @@ -11,6 +11,11 @@ class phpbb_ext_testext_cron_dummy_task extends phpbb_cron_task_base { public static $was_run = 0; + public function get_name() + { + return get_class($this); + } + public function run() { self::$was_run++; diff --git a/tests/cron/includes/cron/task/core/dummy_task.php b/tests/cron/includes/cron/task/core/dummy_task.php index 6e2e2db636..ce3e91a9ba 100644 --- a/tests/cron/includes/cron/task/core/dummy_task.php +++ b/tests/cron/includes/cron/task/core/dummy_task.php @@ -11,6 +11,11 @@ class phpbb_cron_task_core_dummy_task extends phpbb_cron_task_base { public static $was_run = 0; + public function get_name() + { + return get_class($this); + } + public function run() { self::$was_run++; diff --git a/tests/cron/includes/cron/task/core/second_dummy_task.php b/tests/cron/includes/cron/task/core/second_dummy_task.php index 8cd0bddfc0..76a55588f9 100644 --- a/tests/cron/includes/cron/task/core/second_dummy_task.php +++ b/tests/cron/includes/cron/task/core/second_dummy_task.php @@ -11,6 +11,11 @@ class phpbb_cron_task_core_second_dummy_task extends phpbb_cron_task_base { public static $was_run = 0; + public function get_name() + { + return get_class($this); + } + public function run() { self::$was_run++; diff --git a/tests/cron/manager_test.php b/tests/cron/manager_test.php index f433fc9a9b..8ed33b06e2 100644 --- a/tests/cron/manager_test.php +++ b/tests/cron/manager_test.php @@ -19,10 +19,10 @@ class phpbb_cron_manager_test extends PHPUnit_Framework_TestCase { public function setUp() { - $this->manager = new phpbb_cron_manager(array( - 'phpbb_cron_task_core_dummy_task', - 'phpbb_cron_task_core_second_dummy_task', - 'phpbb_ext_testext_cron_dummy_task', + $this->manager = $this->create_cron_manager(array( + new phpbb_cron_task_core_dummy_task(), + new phpbb_cron_task_core_second_dummy_task(), + new phpbb_ext_testext_cron_dummy_task(), )); $this->task_name = 'phpbb_cron_task_core_dummy_task'; } @@ -34,13 +34,6 @@ class phpbb_cron_manager_test extends PHPUnit_Framework_TestCase $this->assertEquals($this->task_name, $task->get_name()); } - public function test_manager_instantiates_task_by_name() - { - $task = $this->manager->instantiate_task($this->task_name, array()); - $this->assertInstanceOf('phpbb_cron_task_wrapper', $task); - $this->assertEquals($this->task_name, $task->get_name()); - } - public function test_manager_finds_all_ready_tasks() { $tasks = $this->manager->find_all_ready_tasks(); @@ -55,10 +48,10 @@ class phpbb_cron_manager_test extends PHPUnit_Framework_TestCase public function test_manager_finds_only_ready_tasks() { - $manager = new phpbb_cron_manager(array( - 'phpbb_cron_task_core_simple_ready', - 'phpbb_cron_task_core_simple_not_runnable', - 'phpbb_cron_task_core_simple_should_not_run', + $manager = $this->create_cron_manager(array( + new phpbb_cron_task_core_simple_ready(), + new phpbb_cron_task_core_simple_not_runnable(), + new phpbb_cron_task_core_simple_should_not_run(), )); $tasks = $manager->find_all_ready_tasks(); $task_names = $this->tasks_to_names($tasks); @@ -70,8 +63,15 @@ class phpbb_cron_manager_test extends PHPUnit_Framework_TestCase $names = array(); foreach ($tasks as $task) { - $names[] = get_class($task->task); + $names[] = $task->get_name(); } return $names; } + + private function create_cron_manager($tasks) + { + global $phpbb_root_path, $phpEx; + + return new phpbb_cron_manager($tasks, $phpbb_root_path, $phpEx); + } } diff --git a/tests/cron/task_provider_test.php b/tests/cron/task_provider_test.php index 4547c61a55..4458d811a5 100644 --- a/tests/cron/task_provider_test.php +++ b/tests/cron/task_provider_test.php @@ -7,37 +7,48 @@ * */ -require_once dirname(__FILE__) . '/../mock/extension_manager.php'; +require_once dirname(__FILE__) . '/includes/cron/task/core/dummy_task.php'; +require_once dirname(__FILE__) . '/includes/cron/task/core/second_dummy_task.php'; +require_once dirname(__FILE__) . '/ext/testext/cron/dummy_task.php'; class phpbb_cron_task_provider_test extends PHPUnit_Framework_TestCase { public function setUp() { - $this->extension_manager = new phpbb_mock_extension_manager( - dirname(__FILE__) . '/', - array( - 'testext' => array( - 'ext_name' => 'testext', - 'ext_active' => true, - 'ext_path' => 'ext/testext/' - ), - )); - $this->provider = new phpbb_cron_task_provider($this->extension_manager); + $this->tasks = array( + 'phpbb_cron_task_core_dummy_task', + 'phpbb_cron_task_core_second_dummy_task', + 'phpbb_ext_testext_cron_dummy_task', + ); + + $container = $this->getMock('Symfony\Component\DependencyInjection\TaggedContainerInterface'); + $container + ->expects($this->once()) + ->method('findTaggedServiceIds') + ->will($this->returnValue(array_flip($this->tasks))); + $container + ->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function ($name) { + return new $name; + })); + + $this->provider = new phpbb_cron_task_provider($container); } public function test_manager_finds_shipped_tasks() { - $tasks = array(); + $task_names = array(); foreach ($this->provider as $task) { - $tasks[] = $task; + $task_names[] = $task->get_name(); } - sort($tasks); + sort($task_names); $this->assertEquals(array( 'phpbb_cron_task_core_dummy_task', 'phpbb_cron_task_core_second_dummy_task', 'phpbb_ext_testext_cron_dummy_task', - ), $tasks); + ), $task_names); } } diff --git a/tests/cron/tasks/simple_not_runnable.php b/tests/cron/tasks/simple_not_runnable.php index 837f28f1c0..56d484eacd 100644 --- a/tests/cron/tasks/simple_not_runnable.php +++ b/tests/cron/tasks/simple_not_runnable.php @@ -2,6 +2,11 @@ class phpbb_cron_task_core_simple_not_runnable extends phpbb_cron_task_base { + public function get_name() + { + return get_class($this); + } + public function run() { } diff --git a/tests/cron/tasks/simple_ready.php b/tests/cron/tasks/simple_ready.php index de5f10e491..8aa0507406 100644 --- a/tests/cron/tasks/simple_ready.php +++ b/tests/cron/tasks/simple_ready.php @@ -2,6 +2,11 @@ class phpbb_cron_task_core_simple_ready extends phpbb_cron_task_base { + public function get_name() + { + return get_class($this); + } + public function run() { } diff --git a/tests/cron/tasks/simple_should_not_run.php b/tests/cron/tasks/simple_should_not_run.php index c2a41616f6..58f6df2616 100644 --- a/tests/cron/tasks/simple_should_not_run.php +++ b/tests/cron/tasks/simple_should_not_run.php @@ -2,6 +2,11 @@ class phpbb_cron_task_core_simple_should_not_run extends phpbb_cron_task_base { + public function get_name() + { + return get_class($this); + } + public function run() { } From e6a1d37634fb9f9f53125460abdaf9c90d8858a7 Mon Sep 17 00:00:00 2001 From: Senky Date: Mon, 9 Apr 2012 16:23:42 +0200 Subject: [PATCH 0056/1142] [ticket/9918] $redirect variable used from now According to comment marc1706 added to tracker http://tracker.phpbb.com/browse/PHPBB3-9918?focusedCommentId=35120&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-35120 I changed files to fit his second advice - redirect is kept in hidden_fields and other code changed. PHPBB3-9918 --- phpBB/includes/mcp/mcp_forum.php | 4 ++-- phpBB/includes/mcp/mcp_topic.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index fec1edc872..889e0a368c 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -433,7 +433,7 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) confirm_box(false, 'MERGE_TOPICS', $s_hidden_fields); } - $redirect = request_var('redirect', "index.$phpEx"); + $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id"); $redirect = reapply_sid($redirect); if (!$success_msg) @@ -442,7 +442,7 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) } else { - meta_refresh(3, append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id")); + meta_refresh(3, $redirect); trigger_error($user->lang[$success_msg] . '

' . $return_link); } } diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index d4ba89b04c..7ed4908280 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -529,7 +529,7 @@ function split_topic($action, $topic_id, $to_forum_id, $subject) confirm_box(false, ($action == 'split_all') ? 'SPLIT_TOPIC_ALL' : 'SPLIT_TOPIC_BEYOND', $s_hidden_fields); } - $redirect = request_var('redirect', "index.$phpEx"); + $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id"); $redirect = reapply_sid($redirect); if (!$success_msg) @@ -538,7 +538,7 @@ function split_topic($action, $topic_id, $to_forum_id, $subject) } else { - meta_refresh(3, append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id")); + meta_refresh(3, $redirect); trigger_error($user->lang[$success_msg] . '

' . $return_link); } } @@ -635,7 +635,7 @@ function merge_posts($topic_id, $to_topic_id) confirm_box(false, 'MERGE_POSTS', $s_hidden_fields); } - $redirect = request_var('redirect', "index.$phpEx"); + $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id"); $redirect = reapply_sid($redirect); if (!$success_msg) @@ -644,7 +644,7 @@ function merge_posts($topic_id, $to_topic_id) } else { - meta_refresh(3, append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id")); + meta_refresh(3, $redirect); trigger_error($user->lang[$success_msg] . '

' . $return_link); } } From 807eccc97632ea8beee4485ef926e4436da2554c Mon Sep 17 00:00:00 2001 From: Senky Date: Sun, 15 Apr 2012 19:13:08 +0200 Subject: [PATCH 0057/1142] [ticket/9918] default values in request_var changed to one string As per marc1706's note, all request_var functions now have only one string in default value PHPBB3-9918 --- phpBB/includes/mcp/mcp_forum.php | 2 +- phpBB/includes/mcp/mcp_topic.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index 889e0a368c..4e40af934c 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -433,7 +433,7 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) confirm_box(false, 'MERGE_TOPICS', $s_hidden_fields); } - $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id"); + $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx?f=$to_forum_id&t=$to_topic_id"); $redirect = reapply_sid($redirect); if (!$success_msg) diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 7ed4908280..e96c025795 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -529,7 +529,7 @@ function split_topic($action, $topic_id, $to_forum_id, $subject) confirm_box(false, ($action == 'split_all') ? 'SPLIT_TOPIC_ALL' : 'SPLIT_TOPIC_BEYOND', $s_hidden_fields); } - $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id"); + $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx?f=$to_forum_id&t=$to_topic_id"); $redirect = reapply_sid($redirect); if (!$success_msg) @@ -635,7 +635,7 @@ function merge_posts($topic_id, $to_topic_id) confirm_box(false, 'MERGE_POSTS', $s_hidden_fields); } - $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id"); + $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx?f=$to_forum_id&t=$to_topic_id"); $redirect = reapply_sid($redirect); if (!$success_msg) From 286aebd93bd49eac2ff80c3a6930f7f692a4c3ef Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 18 Apr 2012 17:01:40 +0200 Subject: [PATCH 0058/1142] [ticket/10811] Fix AJAX callback alt_text so it can be repeated. PHPBB3-10811 --- phpBB/assets/javascript/core.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js index 958b6c9ff6..74c71fca79 100644 --- a/phpBB/assets/javascript/core.js +++ b/phpBB/assets/javascript/core.js @@ -436,11 +436,12 @@ phpbb.add_ajax_callback = function(id, callback) * the alt-text data attribute, and replaces the text in the attribute with the * current text so that the process can be repeated. */ -phpbb.add_ajax_callback('alt_text', function(data) { +phpbb.add_ajax_callback('alt_text', function() { var el = $(this), alt_text; alt_text = el.attr('data-alt-text'); + el.attr('data-alt-text', el.text()); el.attr('title', alt_text); el.text(alt_text); }); From 953e829b527acd6de80fee93bb1bfd08c6010c51 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 25 Mar 2012 20:57:35 -0400 Subject: [PATCH 0059/1142] [feature/prune-users] Non-cosmetic changes per bantu's review. PHPBB3-9622 --- phpBB/includes/acp/acp_prune.php | 1 + phpBB/includes/functions_user.php | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/acp/acp_prune.php b/phpBB/includes/acp/acp_prune.php index 145c0c3854..101eb5a1ed 100644 --- a/phpBB/includes/acp/acp_prune.php +++ b/phpBB/includes/acp/acp_prune.php @@ -343,6 +343,7 @@ class acp_prune { $s_group_list .= '
-
-

' . $user->lang['FULLTEXT_POSTGRES_MBSTRING_EXPLAIN'] . '
-
' . (($this->mbstring_regex) ? $user->lang['YES'] : $user->lang['NO']). '
-

' . $user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '
disabled="diabled" />
+
disabled="disabled" />
From e68c1fb9e4d5de214c1e483531706ec300ffdb0d Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 11 Jul 2012 12:58:57 +0200 Subject: [PATCH 0223/1142] [ticket/10950] Use database count() and group by instead of doing that in php PHPBB3-10950 --- phpBB/includes/functions_privmsgs.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php index 6c0adccbed..1d45961ac4 100644 --- a/phpBB/includes/functions_privmsgs.php +++ b/phpBB/includes/functions_privmsgs.php @@ -1143,7 +1143,7 @@ function phpbb_delete_user_pms($user_id) if (!empty($undelivered_msg)) { - // A pm is not undelivered, if for any receipt the message was moved + // A pm is delivered, if for any receipt the message was moved // from their NO_BOX to another folder. $sql = 'SELECT msg_id FROM ' . PRIVMSGS_TO_TABLE . ' @@ -1165,23 +1165,17 @@ function phpbb_delete_user_pms($user_id) $undelivered_user = array(); // Count the messages we delete, so we can correct the user pm data - $sql = 'SELECT user_id + $sql = 'SELECT user_id, COUNT(msg_id) as num_undelivered_privmsgs FROM ' . PRIVMSGS_TO_TABLE . ' WHERE author_id = ' . $user_id . ' AND folder_id = ' . PRIVMSGS_NO_BOX . ' - AND ' . $db->sql_in_set('msg_id', $undelivered_msg); + AND ' . $db->sql_in_set('msg_id', $undelivered_msg) . ' + GROUP BY user_id'; $result = $db->sql_query($sql); while ($row = $db->sql_fetchrow($result)) { - if (isset($undelivered_user[$row['user_id']])) - { - ++$undelivered_user[$row['user_id']]; - } - else - { - $undelivered_user[$row['user_id']] = 1; - } + $undelivered_user[$row['user_id']] = (int) $row['num_undelivered_privmsgs']; } $db->sql_freeresult($result); From d883535b102ffba8781f485ba94fae237d8376b0 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 11 Jul 2012 13:05:36 +0200 Subject: [PATCH 0224/1142] [ticket/10950] Remove deleted entries in tests instead of commenting them out PHPBB3-10950 --- tests/privmsgs/delete_user_pms_test.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/tests/privmsgs/delete_user_pms_test.php b/tests/privmsgs/delete_user_pms_test.php index e5c0e82712..a586d691e9 100644 --- a/tests/privmsgs/delete_user_pms_test.php +++ b/tests/privmsgs/delete_user_pms_test.php @@ -28,23 +28,10 @@ class phpbb_privmsgs_delete_user_pms_test extends phpbb_database_test_case 2, array( array('msg_id' => 1), - //array('msg_id' => 2), - //array('msg_id' => 3), - //array('msg_id' => 4), - //array('msg_id' => 5), ), array( - //array('msg_id' => 1, 'user_id' => 2), array('msg_id' => 1, 'user_id' => 3), array('msg_id' => 1, 'user_id' => 4), - //array('msg_id' => 2, 'user_id' => 2), - //array('msg_id' => 2, 'user_id' => 4), - //array('msg_id' => 4, 'user_id' => 3), - //array('msg_id' => 3, 'user_id' => 2), - //array('msg_id' => 4, 'user_id' => 3), - //array('msg_id' => 5, 'user_id' => 2), - //array('msg_id' => 5, 'user_id' => 3), - //array('msg_id' => 5, 'user_id' => 4), ), ), array( @@ -53,19 +40,15 @@ class phpbb_privmsgs_delete_user_pms_test extends phpbb_database_test_case array('msg_id' => 1), array('msg_id' => 2), array('msg_id' => 3), - //array('msg_id' => 4), array('msg_id' => 5), ), array( array('msg_id' => 1, 'user_id' => 2), - //array('msg_id' => 1, 'user_id' => 3), array('msg_id' => 1, 'user_id' => 4), array('msg_id' => 2, 'user_id' => 2), array('msg_id' => 2, 'user_id' => 4), array('msg_id' => 3, 'user_id' => 2), - //array('msg_id' => 4, 'user_id' => 3), array('msg_id' => 5, 'user_id' => 2), - //array('msg_id' => 5, 'user_id' => 3), array('msg_id' => 5, 'user_id' => 4), ), ), From 95298de5ae08eab7feead61d38ec6cdd3b76d178 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 11 Jul 2012 14:53:50 +0200 Subject: [PATCH 0225/1142] [ticket/10982] Allow setting dimming control overlay also as data-overlay PHPBB3-10982 --- phpBB/assets/javascript/core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js index 76615eb051..1c5024a1a1 100644 --- a/phpBB/assets/javascript/core.js +++ b/phpBB/assets/javascript/core.js @@ -383,7 +383,7 @@ phpbb.ajaxify = function(options) { return; } - if (overlay) + if (overlay && (!$this.attr('data-overlay') || $this.attr('data-overlay') == true)) { phpbb.loading_alert(); } From 4aabe0cd4d2bc4f0c56d40b5a65d5cf2c3187c1d Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 11 Jul 2012 14:54:31 +0200 Subject: [PATCH 0226/1142] [ticket/10982] Remove overlay on first up/down move of forums in ACP PHPBB3-10982 --- phpBB/adm/style/acp_forums.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/adm/style/acp_forums.html b/phpBB/adm/style/acp_forums.html index 048a24a328..e29cdcf2e9 100644 --- a/phpBB/adm/style/acp_forums.html +++ b/phpBB/adm/style/acp_forums.html @@ -454,12 +454,12 @@ {ICON_MOVE_UP_DISABLED} - {ICON_MOVE_DOWN} + {ICON_MOVE_DOWN} - {ICON_MOVE_UP} - {ICON_MOVE_DOWN} + {ICON_MOVE_UP} + {ICON_MOVE_DOWN} - {ICON_MOVE_UP} + {ICON_MOVE_UP} {ICON_MOVE_DOWN_DISABLED} {ICON_MOVE_UP_DISABLED} From c4c95fddaa865b6639305415a6e0908bb9cd6691 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 11 Jul 2012 15:06:44 +0200 Subject: [PATCH 0227/1142] [ticket/10982] Correctly check, whether data-overlay is set PHPBB3-10982 --- phpBB/assets/javascript/core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js index 1c5024a1a1..6b1b08c424 100644 --- a/phpBB/assets/javascript/core.js +++ b/phpBB/assets/javascript/core.js @@ -383,7 +383,7 @@ phpbb.ajaxify = function(options) { return; } - if (overlay && (!$this.attr('data-overlay') || $this.attr('data-overlay') == true)) + if (overlay && (typeof $this.attr('data-overlay') === 'undefined' || $this.attr('data-overlay') == 'true')) { phpbb.loading_alert(); } From a50b0faf4abfb1bba68e03d843c58f07f842cf12 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Fri, 4 May 2012 18:39:41 +0530 Subject: [PATCH 0228/1142] [feature/sphinx-fulltext-search] MOD by naderman installed in phpbb PHPBB3-10946 --- phpBB/includes/functions_sphinx.php | 507 +++++++++ phpBB/includes/search/fulltext_sphinx.php | 1166 +++++++++++++++++++ phpBB/includes/sphinxapi-0.9.8.php | 1202 ++++++++++++++++++++ phpBB/language/en/mods/fulltext_sphinx.php | 65 ++ 4 files changed, 2940 insertions(+) create mode 100644 phpBB/includes/functions_sphinx.php create mode 100644 phpBB/includes/search/fulltext_sphinx.php create mode 100644 phpBB/includes/sphinxapi-0.9.8.php create mode 100644 phpBB/language/en/mods/fulltext_sphinx.php diff --git a/phpBB/includes/functions_sphinx.php b/phpBB/includes/functions_sphinx.php new file mode 100644 index 0000000000..976f93f77c --- /dev/null +++ b/phpBB/includes/functions_sphinx.php @@ -0,0 +1,507 @@ +read($filename); + } + } + + /** + * Get a section object by its name + * + * @param string $name The name of the section that shall be returned + * @return sphinx_config_section The section object or null if none was found + */ + function &get_section_by_name($name) + { + for ($i = 0, $n = sizeof($this->sections); $i < $n; $i++) + { + // make sure this is really a section object and not a comment + if (is_a($this->sections[$i], 'sphinx_config_section') && $this->sections[$i]->get_name() == $name) + { + return $this->sections[$i]; + } + } + $null = null; + return $null; + } + + /** + * Appends a new empty section to the end of the config + * + * @param string $name The name for the new section + * @return sphinx_config_section The newly created section object + */ + function &add_section($name) + { + $this->sections[] = new sphinx_config_section($name, ''); + return $this->sections[sizeof($this->sections) - 1]; + } + + /** + * Parses the config file at the given path, which is stored in $this->loaded for later use + * + * @param string $filename The path to the config file + */ + function read($filename) + { + // split the file into lines, we'll process it line by line + $config_file = file($filename); + + $this->sections = array(); + + $section = null; + $found_opening_bracket = false; + $in_value = false; + + foreach ($config_file as $i => $line) + { + // if the value of a variable continues to the next line because the line break was escaped + // then we don't trim leading space but treat it as a part of the value + if ($in_value) + { + $line = rtrim($line); + } + else + { + $line = trim($line); + } + + // if we're not inside a section look for one + if (!$section) + { + // add empty lines and comments as comment objects to the section list + // that way they're not deleted when reassembling the file from the sections + if (!$line || $line[0] == '#') + { + $this->sections[] = new sphinx_config_comment($config_file[$i]); + continue; + } + else + { + // otherwise we scan the line reading the section name until we find + // an opening curly bracket or a comment + $section_name = ''; + $section_name_comment = ''; + $found_opening_bracket = false; + for ($j = 0, $n = strlen($line); $j < $n; $j++) + { + if ($line[$j] == '#') + { + $section_name_comment = substr($line, $j); + break; + } + + if ($found_opening_bracket) + { + continue; + } + + if ($line[$j] == '{') + { + $found_opening_bracket = true; + continue; + } + + $section_name .= $line[$j]; + } + + // and then we create the new section object + $section_name = trim($section_name); + $section = new sphinx_config_section($section_name, $section_name_comment); + } + } + else // if we're looking for variables inside a section + { + $skip_first = false; + + // if we're not in a value continuing over the line feed + if (!$in_value) + { + // then add empty lines and comments as comment objects to the variable list + // of this section so they're not deleted on reassembly + if (!$line || $line[0] == '#') + { + $section->add_variable(new sphinx_config_comment($config_file[$i])); + continue; + } + + // as long as we haven't yet actually found an opening bracket for this section + // we treat everything as comments so it's not deleted either + if (!$found_opening_bracket) + { + if ($line[0] == '{') + { + $skip_first = true; + $line = substr($line, 1); + $found_opening_bracket = true; + } + else + { + $section->add_variable(new sphinx_config_comment($config_file[$i])); + continue; + } + } + } + + // if we did not find a comment in this line or still add to the previous line's value ... + if ($line || $in_value) + { + if (!$in_value) + { + $name = ''; + $value = ''; + $comment = ''; + $found_assignment = false; + } + $in_value = false; + $end_section = false; + + // ... then we should prase this line char by char: + // - first there's the variable name + // - then an equal sign + // - the variable value + // - possibly a backslash before the linefeed in this case we need to continue + // parsing the value in the next line + // - a # indicating that the rest of the line is a comment + // - a closing curly bracket indicating the end of this section + for ($j = 0, $n = strlen($line); $j < $n; $j++) + { + if ($line[$j] == '#') + { + $comment = substr($line, $j); + break; + } + else if ($line[$j] == '}') + { + $comment = substr($line, $j + 1); + $end_section = true; + break; + } + else if (!$found_assignment) + { + if ($line[$j] == '=') + { + $found_assignment = true; + } + else + { + $name .= $line[$j]; + } + } + else + { + if ($line[$j] == '\\' && $j == $n - 1) + { + $value .= "\n"; + $in_value = true; + continue 2; // go to the next line and keep processing the value in there + } + $value .= $line[$j]; + } + } + + // if a name and an equal sign were found then we have append a new variable object to the section + if ($name && $found_assignment) + { + $section->add_variable(new sphinx_config_variable(trim($name), trim($value), ($end_section) ? '' : $comment)); + continue; + } + + // if we found a closing curly bracket this section has been completed and we can append it to the section list + // and continue with looking for the next section + if ($end_section) + { + $section->set_end_comment($comment); + $this->sections[] = $section; + $section = null; + continue; + } + } + + // if we did not find anything meaningful up to here, then just treat it as a comment + $comment = ($skip_first) ? "\t" . substr(ltrim($config_file[$i]), 1) : $config_file[$i]; + $section->add_variable(new sphinx_config_comment($comment)); + } + } + + // keep the filename for later use + $this->loaded = $filename; + } + + /** + * Writes the config data into a file + * + * @param string $filename The optional filename into which the config data shall be written. + * If it's not specified it will be written into the file that the config + * was originally read from. + */ + function write($filename = false) + { + if ($filename === false && $this->loaded) + { + $filename = $this->loaded; + } + + $data = ""; + foreach ($this->sections as $section) + { + $data .= $section->to_string(); + } + + $fp = fopen($filename, 'wb'); + fwrite($fp, $data); + fclose($fp); + } +} + +/** +* sphinx_config_section +* Represents a single section inside the sphinx configuration +*/ +class sphinx_config_section +{ + var $name; + var $comment; + var $end_comment; + var $variables = array(); + + /** + * Construct a new section + * + * @param string $name Name of the section + * @param string $comment Comment that should be appended after the name in the + * textual format. + */ + function sphinx_config_section($name, $comment) + { + $this->name = $name; + $this->comment = $comment; + $this->end_comment = ''; + } + + /** + * Add a variable object to the list of variables in this section + * + * @param sphinx_config_variable $variable The variable object + */ + function add_variable($variable) + { + $this->variables[] = $variable; + } + + /** + * Adds a comment after the closing bracket in the textual representation + */ + function set_end_comment($end_comment) + { + $this->end_comment = $end_comment; + } + + /** + * Getter for the name of this section + * + * @return string Section's name + */ + function get_name() + { + return $this->name; + } + + /** + * Get a variable object by its name + * + * @param string $name The name of the variable that shall be returned + * @return sphinx_config_section The first variable object from this section with the + * given name or null if none was found + */ + function &get_variable_by_name($name) + { + for ($i = 0, $n = sizeof($this->variables); $i < $n; $i++) + { + // make sure this is a variable object and not a comment + if (is_a($this->variables[$i], 'sphinx_config_variable') && $this->variables[$i]->get_name() == $name) + { + return $this->variables[$i]; + } + } + $null = null; + return $null; + } + + /** + * Deletes all variables with the given name + * + * @param string $name The name of the variable objects that are supposed to be removed + */ + function delete_variables_by_name($name) + { + for ($i = 0; $i < sizeof($this->variables); $i++) + { + // make sure this is a variable object and not a comment + if (is_a($this->variables[$i], 'sphinx_config_variable') && $this->variables[$i]->get_name() == $name) + { + array_splice($this->variables, $i, 1); + $i--; + } + } + } + + /** + * Create a new variable object and append it to the variable list of this section + * + * @param string $name The name for the new variable + * @param string $value The value for the new variable + * @return sphinx_config_variable Variable object that was created + */ + function &create_variable($name, $value) + { + $this->variables[] = new sphinx_config_variable($name, $value, ''); + return $this->variables[sizeof($this->variables) - 1]; + } + + /** + * Turns this object into a string which can be written to a config file + * + * @return string Config data in textual form, parsable for sphinx + */ + function to_string() + { + $content = $this->name . " " . $this->comment . "\n{\n"; + + // make sure we don't get too many newlines after the opening bracket + while (trim($this->variables[0]->to_string()) == "") + { + array_shift($this->variables); + } + + foreach ($this->variables as $variable) + { + $content .= $variable->to_string(); + } + $content .= '}' . $this->end_comment . "\n"; + + return $content; + } +} + +/** +* sphinx_config_variable +* Represents a single variable inside the sphinx configuration +*/ +class sphinx_config_variable +{ + var $name; + var $value; + var $comment; + + /** + * Constructs a new variable object + * + * @param string $name Name of the variable + * @param string $value Value of the variable + * @param string $comment Optional comment after the variable in the + * config file + */ + function sphinx_config_variable($name, $value, $comment) + { + $this->name = $name; + $this->value = $value; + $this->comment = $comment; + } + + /** + * Getter for the variable's name + * + * @return string The variable object's name + */ + function get_name() + { + return $this->name; + } + + /** + * Allows changing the variable's value + * + * @param string $value New value for this variable + */ + function set_value($value) + { + $this->value = $value; + } + + /** + * Turns this object into a string readable by sphinx + * + * @return string Config data in textual form + */ + function to_string() + { + return "\t" . $this->name . ' = ' . str_replace("\n", "\\\n", $this->value) . ' ' . $this->comment . "\n"; + } +} + + +/** +* sphinx_config_comment +* Represents a comment inside the sphinx configuration +*/ +class sphinx_config_comment +{ + var $exact_string; + + /** + * Create a new comment + * + * @param string $exact_string The content of the comment including newlines, leading whitespace, etc. + */ + function sphinx_config_comment($exact_string) + { + $this->exact_string = $exact_string; + } + + /** + * Simply returns the comment as it was created + * + * @return string The exact string that was specified in the constructor + */ + function to_string() + { + return $this->exact_string; + } +} + +?> \ No newline at end of file diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php new file mode 100644 index 0000000000..e0c467df93 --- /dev/null +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -0,0 +1,1166 @@ +id = $config['avatar_salt']; + $this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main'; + + $this->sphinx = new SphinxClient (); + + if (!empty($config['fulltext_sphinx_configured'])) + { + if ($config['fulltext_sphinx_autorun'] && !file_exists($config['fulltext_sphinx_data_path'] . 'searchd.pid') && $this->index_created(true)) + { + $this->shutdown_searchd(); +// $cwd = getcwd(); +// chdir($config['fulltext_sphinx_bin_path']); + exec($config['fulltext_sphinx_bin_path'] . SEARCHD_NAME . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf >> ' . $config['fulltext_sphinx_data_path'] . 'log/searchd-startup.log 2>&1 &'); +// chdir($cwd); + } + + // we only support localhost for now + $this->sphinx->SetServer('localhost', (isset($config['fulltext_sphinx_port']) && $config['fulltext_sphinx_port']) ? (int) $config['fulltext_sphinx_port'] : 3312); + } + + $config['fulltext_sphinx_min_word_len'] = 2; + $config['fulltext_sphinx_max_word_len'] = 400; + + $error = false; + } + + /** + * Checks permissions and paths, if everything is correct it generates the config file + */ + function init() + { + global $db, $user, $config; + + if ($db->sql_layer != 'mysql' && $db->sql_layer != 'mysql4' && $db->sql_layer != 'mysqli') + { + return $user->lang['FULLTEXT_SPHINX_WRONG_DATABASE']; + } + + if ($error = $this->config_updated()) + { + return $error; + } + + // move delta to main index each hour + set_config('search_gc', 3600); + + return false; + } + + function config_updated() + { + global $db, $user, $config, $phpbb_root_path, $phpEx; + + if ($config['fulltext_sphinx_autoconf']) + { + $paths = array('fulltext_sphinx_bin_path', 'fulltext_sphinx_config_path', 'fulltext_sphinx_data_path'); + + // check for completeness and add trailing slash if it's not present + foreach ($paths as $path) + { + if (empty($config[$path])) + { + return $user->lang['FULLTEXT_SPHINX_UNCONFIGURED']; + } + if ($config[$path] && substr($config[$path], -1) != '/') + { + set_config($path, $config[$path] . '/'); + } + } + } + + $executables = array( + $config['fulltext_sphinx_bin_path'] . INDEXER_NAME, + $config['fulltext_sphinx_bin_path'] . SEARCHD_NAME, + ); + + if ($config['fulltext_sphinx_autorun']) + { + foreach ($executables as $executable) + { + if (!file_exists($executable)) + { + return sprintf($user->lang['FULLTEXT_SPHINX_FILE_NOT_FOUND'], $executable); + } + + if (!function_exists('exec')) + { + return $user->lang['FULLTEXT_SPHINX_REQUIRES_EXEC']; + } + + $output = array(); + @exec($executable, $output); + + $output = implode("\n", $output); + if (strpos($output, 'Sphinx ') === false) + { + return sprintf($user->lang['FULLTEXT_SPHINX_FILE_NOT_EXECUTABLE'], $executable); + } + } + } + + $writable_paths = array( + $config['fulltext_sphinx_config_path'] => array('config' => 'fulltext_sphinx_autoconf', 'subdir' => false), + $config['fulltext_sphinx_data_path'] => array('config' => 'fulltext_sphinx_autorun', 'subdir' => 'log'), + $config['fulltext_sphinx_data_path'] . 'log/' => array('config' => 'fulltext_sphinx_autorun', 'subdir' => false), + ); + + foreach ($writable_paths as $path => $info) + { + if ($config[$info['config']]) + { + // make sure directory exists + // if we could drop the @ here and figure out whether the file really + // doesn't exist or whether open_basedir is in effect, would be nice + if (!@file_exists($path)) + { + return sprintf($user->lang['FULLTEXT_SPHINX_DIRECTORY_NOT_FOUND'], $path); + } + + // now check if it is writable by storing a simple file + $filename = $path . 'write_test'; + $fp = @fopen($filename, 'wb'); + if ($fp === false) + { + return sprintf($user->lang['FULLTEXT_SPHINX_FILE_NOT_WRITABLE'], $filename); + } + @fclose($fp); + + @unlink($filename); + + if ($info['subdir'] !== false) + { + if (!is_dir($path . $info['subdir'])) + { + mkdir($path . $info['subdir']); + } + } + } + } + + if ($config['fulltext_sphinx_autoconf']) + { + include ($phpbb_root_path . 'config.' . $phpEx); + + // now that we're sure everything was entered correctly, generate a config for the index + // we misuse the avatar_salt for this, as it should be unique ;-) + + if (!class_exists('sphinx_config')) + { + include($phpbb_root_path . 'includes/functions_sphinx.php'); + } + + if (!file_exists($config['fulltext_sphinx_config_path'] . 'sphinx.conf')) + { + $filename = $config['fulltext_sphinx_config_path'] . 'sphinx.conf'; + $fp = @fopen($filename, 'wb'); + if ($fp === false) + { + return sprintf($user->lang['FULLTEXT_SPHINX_FILE_NOT_WRITABLE'], $filename); + } + @fclose($fp); + } + + $config_object = new sphinx_config($config['fulltext_sphinx_config_path'] . 'sphinx.conf'); + + $config_data = array( + "source source_phpbb_{$this->id}_main" => array( + array('type', 'mysql'), + array('sql_host', $dbhost), + array('sql_user', $dbuser), + array('sql_pass', $dbpasswd), + array('sql_db', $dbname), + array('sql_port', $dbport), + array('sql_query_pre', 'SET NAMES utf8'), + array('sql_query_pre', 'REPLACE INTO ' . SPHINX_TABLE . ' SELECT 1, MAX(post_id) FROM ' . POSTS_TABLE . ''), + array('sql_query_range', 'SELECT MIN(post_id), MAX(post_id) FROM ' . POSTS_TABLE . ''), + array('sql_range_step', '5000'), + array('sql_query', 'SELECT + p.post_id AS id, + p.forum_id, + p.topic_id, + p.poster_id, + IF(p.post_id = t.topic_first_post_id, 1, 0) as topic_first_post, + p.post_time, + p.post_subject, + p.post_subject as title, + p.post_text as data, + t.topic_last_post_time, + 0 as deleted + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t + WHERE + p.topic_id = t.topic_id + AND p.post_id >= $start AND p.post_id <= $end'), + array('sql_query_post', ''), + array('sql_query_post_index', 'REPLACE INTO ' . SPHINX_TABLE . ' ( counter_id, max_doc_id ) VALUES ( 1, $maxid )'), + array('sql_query_info', 'SELECT * FROM ' . POSTS_TABLE . ' WHERE post_id = $id'), + array('sql_attr_uint', 'forum_id'), + array('sql_attr_uint', 'topic_id'), + array('sql_attr_uint', 'poster_id'), + array('sql_attr_bool', 'topic_first_post'), + array('sql_attr_bool', 'deleted'), + array('sql_attr_timestamp' , 'post_time'), + array('sql_attr_timestamp' , 'topic_last_post_time'), + array('sql_attr_str2ordinal', 'post_subject'), + ), + "source source_phpbb_{$this->id}_delta : source_phpbb_{$this->id}_main" => array( + array('sql_query_pre', ''), + array('sql_query_range', ''), + array('sql_range_step', ''), + array('sql_query', 'SELECT + p.post_id AS id, + p.forum_id, + p.topic_id, + p.poster_id, + IF(p.post_id = t.topic_first_post_id, 1, 0) as topic_first_post, + p.post_time, + p.post_subject, + p.post_subject as title, + p.post_text as data, + t.topic_last_post_time, + 0 as deleted + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t + WHERE + p.topic_id = t.topic_id + AND p.post_id >= ( SELECT max_doc_id FROM ' . SPHINX_TABLE . ' WHERE counter_id=1 )'), + ), + "index index_phpbb_{$this->id}_main" => array( + array('path', $config['fulltext_sphinx_data_path'] . "index_phpbb_{$this->id}_main"), + array('source', "source_phpbb_{$this->id}_main"), + array('docinfo', 'extern'), + array('morphology', 'none'), + array('stopwords', (file_exists($config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt') && $config['fulltext_sphinx_stopwords']) ? $config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt' : ''), + array('min_word_len', '2'), + array('charset_type', 'utf-8'), + array('charset_table', 'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+Ăź410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'), + array('min_prefix_len', '0'), + array('min_infix_len', '0'), + ), + "index index_phpbb_{$this->id}_delta : index_phpbb_{$this->id}_main" => array( + array('path', $config['fulltext_sphinx_data_path'] . "index_phpbb_{$this->id}_delta"), + array('source', "source_phpbb_{$this->id}_delta"), + ), + 'indexer' => array( + array('mem_limit', $config['fulltext_sphinx_indexer_mem_limit'] . 'M'), + ), + 'searchd' => array( + array('address' , '127.0.0.1'), + array('port', ($config['fulltext_sphinx_port']) ? $config['fulltext_sphinx_port'] : '3312'), + array('log', $config['fulltext_sphinx_data_path'] . "log/searchd.log"), + array('query_log', $config['fulltext_sphinx_data_path'] . "log/sphinx-query.log"), + array('read_timeout', '5'), + array('max_children', '30'), + array('pid_file', $config['fulltext_sphinx_data_path'] . "searchd.pid"), + array('max_matches', (string) MAX_MATCHES), + ), + ); + + $non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true); + $delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true); + + foreach ($config_data as $section_name => $section_data) + { + $section = &$config_object->get_section_by_name($section_name); + if (!$section) + { + $section = &$config_object->add_section($section_name); + } + + foreach ($delete as $key => $void) + { + $section->delete_variables_by_name($key); + } + + foreach ($non_unique as $key => $void) + { + $section->delete_variables_by_name($key); + } + + foreach ($section_data as $entry) + { + $key = $entry[0]; + $value = $entry[1]; + + if (!isset($non_unique[$key])) + { + $variable = &$section->get_variable_by_name($key); + if (!$variable) + { + $variable = &$section->create_variable($key, $value); + } + else + { + $variable->set_value($value); + } + } + else + { + $variable = &$section->create_variable($key, $value); + } + } + } + + $config_object->write($config['fulltext_sphinx_config_path'] . 'sphinx.conf'); + } + + set_config('fulltext_sphinx_configured', '1'); + + $this->shutdown_searchd(); + $this->tidy(); + + return false; + } + + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * Stores the tidied search query in $this->search_query + * + * @param string $keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return false if no valid keywords were found and otherwise true + */ + function split_keywords(&$keywords, $terms) + { + global $config; + + if ($terms == 'all') + { + $match = array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#'); + $replace = array(' & ', ' | ', ' - ', ' +', ' -', ' |', ''); + + $replacements = 0; + $keywords = preg_replace($match, $replace, $keywords); + $this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED); + } + else + { + $this->sphinx->SetMatchMode(SPH_MATCH_ANY); + } + + $match = array(); + // Keep quotes + $match[] = "#"#"; + // KeepNew lines + $match[] = "#[\n]+#"; + + $replace = array('"', " "); + + $keywords = str_replace(array('"', "\n"), array('"', ' '), trim($keywords)); + + if (strlen($keywords) > 0) + { + $this->search_query = str_replace('"', '"', $keywords); + return true; + } + + return false; + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first. + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param array $m_approve_fid_ary specifies an array of forum ids in which the searcher is allowed to view unapproved posts + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + * + * @access public + */ + function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + { + global $config, $db, $auth; + + // No keywords? No posts. + if (!strlen($this->search_query) && !sizeof($author_ary)) + { + return false; + } + + $id_ary = array(); + + $join_topic = ($type == 'posts') ? false : true; + + // sorting + + if ($type == 'topics') + { + switch ($sort_key) + { + case 'a': + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + case 'f': + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + case 'i': + case 's': + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + case 't': + default: + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + } + } + else + { + switch ($sort_key) + { + case 'a': + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id'); + break; + case 'f': + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id'); + break; + case 'i': + case 's': + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject'); + break; + case 't': + default: + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time'); + break; + } + } + + // most narrow filters first + if ($topic_id) + { + $this->sphinx->SetFilter('topic_id', array($topic_id)); + } + + $search_query_prefix = ''; + + switch($fields) + { + case 'titleonly': + // only search the title + if ($terms == 'all') + { + $search_query_prefix = '@title '; + } + $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); // weight for the title + $this->sphinx->SetFilter('topic_first_post', array(1)); // 1 is first_post, 0 is not first post + break; + + case 'msgonly': + // only search the body + if ($terms == 'all') + { + $search_query_prefix = '@data '; + } + $this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5)); // weight for the body + break; + + case 'firstpost': + $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); // more relative weight for the title, also search the body + $this->sphinx->SetFilter('topic_first_post', array(1)); // 1 is first_post, 0 is not first post + break; + + default: + $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); // more relative weight for the title, also search the body + break; + } + + if (sizeof($author_ary)) + { + $this->sphinx->SetFilter('poster_id', $author_ary); + } + + if (sizeof($ex_fid_ary)) + { + // All forums that a user is allowed to access + $fid_ary = array_unique(array_intersect(array_keys($auth->acl_getf('f_read', true)), array_keys($auth->acl_getf('f_search', true)))); + // All forums that the user wants to and can search in + $search_forums = array_diff($fid_ary, $ex_fid_ary); + + if (sizeof($search_forums)) + { + $this->sphinx->SetFilter('forum_id', $search_forums); + } + } + + $this->sphinx->SetFilter('deleted', array(0)); + + $this->sphinx->SetLimits($start, (int) $per_page, MAX_MATCHES); + $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); + + // could be connection to localhost:3312 failed (errno=111, msg=Connection refused) during rotate, retry if so + $retries = CONNECT_RETRIES; + while (!$result && (strpos($this->sphinx->_error, "errno=111,") !== false) && $retries--) + { + usleep(CONNECT_WAIT_TIME); + $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); + } + + $id_ary = array(); + if (isset($result['matches'])) + { + if ($type == 'posts') + { + $id_ary = array_keys($result['matches']); + } + else + { + foreach($result['matches'] as $key => $value) + { + $id_ary[] = $value['attrs']['topic_id']; + } + } + } + else + { + return false; + } + + $result_count = $result['total_found']; + + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param array $m_approve_fid_ary specifies an array of forum ids in which the searcher is allowed to view unapproved posts + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + * + * @access public + */ + function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + { + $this->search_query = ''; + + $this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN); + $fields = ($firstpost_only) ? 'firstpost' : 'all'; + $terms = 'all'; + return $this->keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, $id_ary, $start, $per_page); + } + + /** + * Updates wordlist and wordmatch tables when a message is posted or changed + * + * @param string $mode Contains the post mode: edit, post, reply, quote + * @param int $post_id The id of the post which is modified/created + * @param string &$message New or updated post content + * @param string &$subject New or updated post subject + * @param int $poster_id Post author's user id + * @param int $forum_id The id of the forum in which the post is located + * + * @access public + */ + function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + global $config, $db; + + if ($mode == 'edit') + { + $this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int)$post_id => array((int)$forum_id, (int)$poster_id))); + } + else if ($mode != 'post' && $post_id) + { + // update topic_last_post_time for full topic + $sql = 'SELECT p1.post_id + FROM ' . POSTS_TABLE . ' p1 + LEFT JOIN ' . POSTS_TABLE . ' p2 ON (p1.topic_id = p2.topic_id) + WHERE p2.post_id = ' . $post_id; + $result = $db->sql_query($sql); + + $post_updates = array(); + $post_time = time(); + while ($row = $db->sql_fetchrow($result)) + { + $post_updates[(int)$row['post_id']] = array((int) $post_time); + } + $db->sql_freeresult($result); + + if (sizeof($post_updates)) + { + $this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates); + } + } + + if ($config['fulltext_sphinx_autorun']) + { + if ($this->index_created()) + { + $rotate = ($this->searchd_running()) ? ' --rotate' : ''; + + $cwd = getcwd(); + chdir($config['fulltext_sphinx_bin_path']); + exec('./' . INDEXER_NAME . $rotate . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf index_phpbb_' . $this->id . '_delta >> ' . $config['fulltext_sphinx_data_path'] . 'log/indexer.log 2>&1 &'); + var_dump('./' . INDEXER_NAME . $rotate . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf index_phpbb_' . $this->id . '_delta >> ' . $config['fulltext_sphinx_data_path'] . 'log/indexer.log 2>&1 &'); + chdir($cwd); + } + } + } + + /** + * Delete a post from the index after it was deleted + */ + function index_remove($post_ids, $author_ids, $forum_ids) + { + $values = array(); + foreach ($post_ids as $post_id) + { + $values[$post_id] = array(1); + } + + $this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values); + } + + /** + * Destroy old cache entries + */ + function tidy($create = false) + { + global $config; + + if ($config['fulltext_sphinx_autorun']) + { + if ($this->index_created() || $create) + { + $rotate = ($this->searchd_running()) ? ' --rotate' : ''; + + $cwd = getcwd(); + chdir($config['fulltext_sphinx_bin_path']); + exec('./' . INDEXER_NAME . $rotate . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf index_phpbb_' . $this->id . '_main >> ' . $config['fulltext_sphinx_data_path'] . 'log/indexer.log 2>&1 &'); + exec('./' . INDEXER_NAME . $rotate . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf index_phpbb_' . $this->id . '_delta >> ' . $config['fulltext_sphinx_data_path'] . 'log/indexer.log 2>&1 &'); + chdir($cwd); + } + } + + set_config('search_last_gc', time(), true); + } + + /** + * Create sphinx table + */ + function create_index($acp_module, $u_action) + { + global $db, $user, $config; + + $this->shutdown_searchd(); + + if (!isset($config['fulltext_sphinx_configured']) || !$config['fulltext_sphinx_configured']) + { + $user->add_lang('mods/fulltext_sphinx'); + + return $user->lang['FULLTEXT_SPHINX_CONFIGURE_FIRST']; + } + + if (!$this->index_created()) + { + $sql = 'CREATE TABLE IF NOT EXISTS ' . SPHINX_TABLE . ' ( + counter_id INT NOT NULL PRIMARY KEY, + max_doc_id INT NOT NULL + )'; + $db->sql_query($sql); + + $sql = 'TRUNCATE TABLE ' . SPHINX_TABLE; + $db->sql_query($sql); + } + + // start indexing process + $this->tidy(true); + + $this->shutdown_searchd(); + + return false; + } + + /** + * Drop sphinx table + */ + function delete_index($acp_module, $u_action) + { + global $db, $config; + + $this->shutdown_searchd(); + + if ($config['fulltext_sphinx_autorun']) + { + sphinx_unlink_by_pattern($config['fulltext_sphinx_data_path'], '#^index_phpbb_' . $this->id . '.*$#'); + } + + if (!$this->index_created()) + { + return false; + } + + $sql = 'DROP TABLE ' . SPHINX_TABLE; + $db->sql_query($sql); + + $this->shutdown_searchd(); + + return false; + } + + /** + * Returns true if the sphinx table was created + */ + function index_created($allow_new_files = true) + { + global $db, $config; + + $sql = 'SHOW TABLES LIKE \'' . SPHINX_TABLE . '\''; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $created = false; + + if ($row) + { + if ($config['fulltext_sphinx_autorun']) + { + if ((file_exists($config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main.spd') && file_exists($config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta.spd')) || ($allow_new_files && file_exists($config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main.new.spd') && file_exists($config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta.new.spd'))) + { + $created = true; + } + } + else + { + $created = true; + } + } + + return $created; + } + + /** + * Kills the searchd process and makes sure there's no locks left over + */ + function shutdown_searchd() + { + global $config; + + if ($config['fulltext_sphinx_autorun']) + { + if (!function_exists('exec')) + { + set_config('fulltext_sphinx_autorun', '0'); + return; + } + + exec('killall -9 ' . SEARCHD_NAME . ' >> /dev/null 2>&1 &'); + + if (file_exists($config['fulltext_sphinx_data_path'] . 'searchd.pid')) + { + unlink($config['fulltext_sphinx_data_path'] . 'searchd.pid'); + } + + sphinx_unlink_by_pattern($config['fulltext_sphinx_data_path'], '#^.*\.spl$#'); + } + } + + /** + * Checks whether searchd is running, if it's not running it makes sure there's no left over + * files by calling shutdown_searchd. + * + * @return boolean Whether searchd is running or not + */ + function searchd_running() + { + global $config; + + // if we cannot manipulate the service assume it is running + if (!$config['fulltext_sphinx_autorun']) + { + return true; + } + + if (file_exists($config['fulltext_sphinx_data_path'] . 'searchd.pid')) + { + $pid = trim(file_get_contents($config['fulltext_sphinx_data_path'] . 'searchd.pid')); + + if ($pid) + { + $output = array(); + $pidof_command = 'pidof'; + + exec('whereis -b pidof', $output); + if (sizeof($output) > 1) + { + $output = explode(' ', trim($output[0])); + $pidof_command = $output[1]; // 0 is pidof: + } + + $output = array(); + exec($pidof_command . ' ' . SEARCHD_NAME, $output); + if (sizeof($output) && (trim($output[0]) == $pid || trim($output[1]) == $pid)) + { + return true; + } + } + } + + // make sure it's really not running + $this->shutdown_searchd(); + + return false; + } + + /** + * Returns an associative array containing information about the indexes + */ + function index_stats() + { + global $user; + + if (empty($this->stats)) + { + $this->get_stats(); + } + + $user->add_lang('mods/fulltext_sphinx'); + + return array( + $user->lang['FULLTEXT_SPHINX_MAIN_POSTS'] => ($this->index_created()) ? $this->stats['main_posts'] : 0, + $user->lang['FULLTEXT_SPHINX_DELTA_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0, + $user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + $user->lang['FULLTEXT_SPHINX_LAST_SEARCHES'] => nl2br($this->stats['last_searches']), + ); + } + + /** + * Collects stats that can be displayed on the index maintenance page + */ + function get_stats() + { + global $db, $config; + + if ($this->index_created()) + { + $sql = 'SELECT COUNT(post_id) as total_posts + FROM ' . POSTS_TABLE; + $result = $db->sql_query($sql); + $this->stats['total_posts'] = (int) $db->sql_fetchfield('total_posts'); + $db->sql_freeresult($result); + + $sql = 'SELECT COUNT(p.post_id) as main_posts + FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m + WHERE p.post_id <= m.max_doc_id + AND m.counter_id = 1'; + $result = $db->sql_query($sql); + $this->stats['main_posts'] = (int) $db->sql_fetchfield('main_posts'); + $db->sql_freeresult($result); + } + + $this->stats['last_searches'] = ''; + if ($config['fulltext_sphinx_autorun']) + { + if (file_exists($config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log')) + { + $last_searches = explode("\n", utf8_htmlspecialchars(sphinx_read_last_lines($config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log', 3))); + + foreach($last_searches as $i => $search) + { + if (strpos($search, '[' . $this->indexes . ']') !== false) + { + $last_searches[$i] = str_replace('[' . $this->indexes . ']', '', $search); + } + else + { + $last_searches[$i] = ''; + } + } + $this->stats['last_searches'] = implode("\n", $last_searches); + } + } + } + + /** + * Returns a list of options for the ACP to display + */ + function acp() + { + global $user, $config; + + $user->add_lang('mods/fulltext_sphinx'); + + $config_vars = array( + 'fulltext_sphinx_autoconf' => 'bool', + 'fulltext_sphinx_autorun' => 'bool', + 'fulltext_sphinx_config_path' => 'string', + 'fulltext_sphinx_data_path' => 'string', + 'fulltext_sphinx_bin_path' => 'string', + 'fulltext_sphinx_port' => 'int', + 'fulltext_sphinx_stopwords' => 'bool', + 'fulltext_sphinx_indexer_mem_limit' => 'int', + ); + + $defaults = array( + 'fulltext_sphinx_autoconf' => '1', + 'fulltext_sphinx_autorun' => '1', + 'fulltext_sphinx_indexer_mem_limit' => '512', + ); + + foreach ($config_vars as $config_var => $type) + { + if (!isset($config[$config_var])) + { + $default = ''; + if (isset($defaults[$config_var])) + { + $default = $defaults[$config_var]; + } + set_config($config_var, $default); + } + } + + $no_autoconf = false; + $no_autorun = false; + $bin_path = $config['fulltext_sphinx_bin_path']; + + // try to guess the path if it is empty + if (empty($bin_path)) + { + if (@file_exists('/usr/local/bin/' . INDEXER_NAME) && @file_exists('/usr/local/bin/' . SEARCHD_NAME)) + { + $bin_path = '/usr/local/bin/'; + } + else if (@file_exists('/usr/bin/' . INDEXER_NAME) && @file_exists('/usr/bin/' . SEARCHD_NAME)) + { + $bin_path = '/usr/bin/'; + } + else + { + $output = array(); + if (!function_exists('exec') || null === @exec('whereis -b ' . INDEXER_NAME, $output)) + { + $no_autorun = true; + } + else if (sizeof($output)) + { + $output = explode(' ', $output[0]); + array_shift($output); // remove indexer: + + foreach ($output as $path) + { + $path = dirname($path) . '/'; + + if (file_exists($path . INDEXER_NAME) && file_exists($path . SEARCHD_NAME)) + { + $bin_path = $path; + break; + } + } + } + } + } + + if ($no_autorun) + { + set_config('fulltext_sphinx_autorun', '0'); + } + + if ($no_autoconf) + { + set_config('fulltext_sphinx_autoconf', '0'); + } + + // rewrite config if fulltext sphinx is enabled + if ($config['fulltext_sphinx_autoconf'] && isset($config['fulltext_sphinx_configured']) && $config['fulltext_sphinx_configured']) + { + $this->config_updated(); + } + + // check whether stopwords file is available and enabled + if (@file_exists($config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt')) + { + $stopwords_available = true; + $stopwords_active = $config['fulltext_sphinx_stopwords']; + } + else + { + $stopwords_available = false; + $stopwords_active = false; + set_config('fulltext_sphinx_stopwords', '0'); + } + + $tpl = ' + ' . $user->lang['FULLTEXT_SPHINX_CONFIGURE_BEFORE']. ' +
+

' . $user->lang['FULLTEXT_SPHINX_AUTOCONF_EXPLAIN'] . '
+
+
+
+

' . $user->lang['FULLTEXT_SPHINX_AUTORUN_EXPLAIN'] . '
+
+
+
+

' . $user->lang['FULLTEXT_SPHINX_CONFIG_PATH_EXPLAIN'] . '
+
+
+
+

' . $user->lang['FULLTEXT_SPHINX_BIN_PATH_EXPLAIN'] . '
+
+
+
+

' . $user->lang['FULLTEXT_SPHINX_DATA_PATH_EXPLAIN'] . '
+
+
+ ' . $user->lang['FULLTEXT_SPHINX_CONFIGURE_AFTER']. ' +
+

' . $user->lang['FULLTEXT_SPHINX_STOPWORDS_FILE_EXPLAIN'] . '
+
+
+
+

' . $user->lang['FULLTEXT_SPHINX_PORT_EXPLAIN'] . '
+
+
+
+

' . $user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN'] . '
+
' . $user->lang['MIB'] . '
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => $config_vars + ); + } +} + +/** +* Deletes all files from a directory that match a certain pattern +* +* @param string $path Path from which files shall be deleted +* @param string $pattern PCRE pattern that a file needs to match in order to be deleted +*/ +function sphinx_unlink_by_pattern($path, $pattern) +{ + $dir = opendir($path); + while (false !== ($file = readdir($dir))) + { + if (is_file($path . $file) && preg_match($pattern, $file)) + { + unlink($path . $file); + } + } + closedir($dir); +} + +/** +* Reads the last from a file +* +* @param string $file The filename from which the lines shall be read +* @param int $amount The number of lines to be read from the end +* @return string Last lines of the file +*/ +function sphinx_read_last_lines($file, $amount) +{ + $fp = fopen($file, 'r'); + fseek($fp, 0, SEEK_END); + + $c = ''; + $i = 0; + + while ($i < $amount) + { + fseek($fp, -2, SEEK_CUR); + $c = fgetc($fp); + if ($c == "\n") + { + $i++; + } + if (feof($fp)) + { + break; + } + } + + $string = fread($fp, 8192); + fclose($fp); + + return $string; +} + +?> \ No newline at end of file diff --git a/phpBB/includes/sphinxapi-0.9.8.php b/phpBB/includes/sphinxapi-0.9.8.php new file mode 100644 index 0000000000..6a7ea17760 --- /dev/null +++ b/phpBB/includes/sphinxapi-0.9.8.php @@ -0,0 +1,1202 @@ +=8 ) + { + $i = (int)$v; + return pack ( "NN", $i>>32, $i&((1<<32)-1) ); + } + + // x32 route, bcmath + $x = "4294967296"; + if ( function_exists("bcmul") ) + { + $h = bcdiv ( $v, $x, 0 ); + $l = bcmod ( $v, $x ); + return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit + } + + // x32 route, 15 or less decimal digits + // we can use float, because its actually double and has 52 precision bits + if ( strlen($v)<=15 ) + { + $f = (float)$v; + $h = (int)($f/$x); + $l = (int)($f-$x*$h); + return pack ( "NN", $h, $l ); + } + + // x32 route, 16 or more decimal digits + // well, let me know if you *really* need this + die ( "INTERNAL ERROR: packing more than 15-digit numeric on 32-bit PHP is not implemented yet (contact support)" ); +} + + +/// portably unpack 64 unsigned bits, network order to numeric +function sphUnpack64 ( $v ) +{ + list($h,$l) = array_values ( unpack ( "N*N*", $v ) ); + + // x64 route + if ( PHP_INT_SIZE>=8 ) + { + if ( $h<0 ) $h += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again + if ( $l<0 ) $l += (1<<32); + return ($h<<32) + $l; + } + + // x32 route + $h = sprintf ( "%u", $h ); + $l = sprintf ( "%u", $l ); + $x = "4294967296"; + + // bcmath + if ( function_exists("bcmul") ) + return bcadd ( $l, bcmul ( $x, $h ) ); + + // no bcmath, 15 or less decimal digits + // we can use float, because its actually double and has 52 precision bits + if ( $h<1048576 ) + { + $f = ((float)$h)*$x + (float)$l; + return sprintf ( "%.0f", $f ); // builtin conversion is only about 39-40 bits precise! + } + + // x32 route, 16 or more decimal digits + // well, let me know if you *really* need this + die ( "INTERNAL ERROR: unpacking more than 15-digit numeric on 32-bit PHP is not implemented yet (contact support)" ); +} + + +/// sphinx searchd client class +class SphinxClient +{ + var $_host; ///< searchd host (default is "localhost") + var $_port; ///< searchd port (default is 3312) + var $_offset; ///< how many records to seek from result-set start (default is 0) + var $_limit; ///< how many records to return from result-set starting at offset (default is 20) + var $_mode; ///< query matching mode (default is SPH_MATCH_ALL) + var $_weights; ///< per-field weights (default is 1 for all fields) + var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE) + var $_sortby; ///< attribute to sort by (defualt is "") + var $_min_id; ///< min ID to match (default is 0, which means no limit) + var $_max_id; ///< max ID to match (default is 0, which means no limit) + var $_filters; ///< search filters + var $_groupby; ///< group-by attribute name + var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with) + var $_groupsort; ///< group-by sorting clause (to sort groups in result set with) + var $_groupdistinct;///< group-by count-distinct attribute + var $_maxmatches; ///< max matches to retrieve + var $_cutoff; ///< cutoff to stop searching at (default is 0) + var $_retrycount; ///< distributed retries count + var $_retrydelay; ///< distributed retries delay + var $_anchor; ///< geographical anchor point + var $_indexweights; ///< per-index weights + var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25) + var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit) + var $_fieldweights; ///< per-field-name weights + + var $_error; ///< last error message + var $_warning; ///< last warning message + + var $_reqs; ///< requests array for multi-query + var $_mbenc; ///< stored mbstring encoding + var $_arrayresult; ///< whether $result["matches"] should be a hash or an array + var $_timeout; ///< connect timeout + + ///////////////////////////////////////////////////////////////////////////// + // common stuff + ///////////////////////////////////////////////////////////////////////////// + + /// create a new client object and fill defaults + function SphinxClient () + { + // per-client-object settings + $this->_host = "localhost"; + $this->_port = 3312; + + // per-query settings + $this->_offset = 0; + $this->_limit = 20; + $this->_mode = SPH_MATCH_ALL; + $this->_weights = array (); + $this->_sort = SPH_SORT_RELEVANCE; + $this->_sortby = ""; + $this->_min_id = 0; + $this->_max_id = 0; + $this->_filters = array (); + $this->_groupby = ""; + $this->_groupfunc = SPH_GROUPBY_DAY; + $this->_groupsort = "@group desc"; + $this->_groupdistinct= ""; + $this->_maxmatches = 1000; + $this->_cutoff = 0; + $this->_retrycount = 0; + $this->_retrydelay = 0; + $this->_anchor = array (); + $this->_indexweights= array (); + $this->_ranker = SPH_RANK_PROXIMITY_BM25; + $this->_maxquerytime= 0; + $this->_fieldweights= array(); + + $this->_error = ""; // per-reply fields (for single-query case) + $this->_warning = ""; + $this->_reqs = array (); // requests storage (for multi-query case) + $this->_mbenc = ""; + $this->_arrayresult = false; + $this->_timeout = 0; + } + + /// get last error message (string) + function GetLastError () + { + return $this->_error; + } + + /// get last warning message (string) + function GetLastWarning () + { + return $this->_warning; + } + + /// set searchd host name (string) and port (integer) + function SetServer ( $host, $port ) + { + assert ( is_string($host) ); + assert ( is_int($port) ); + $this->_host = $host; + $this->_port = $port; + } + + /// set server connection timeout (0 to remove) + function SetConnectTimeout ( $timeout ) + { + assert ( is_numeric($timeout) ); + $this->_timeout = $timeout; + } + + ///////////////////////////////////////////////////////////////////////////// + + /// enter mbstring workaround mode + function _MBPush () + { + $this->_mbenc = ""; + if ( ini_get ( "mbstring.func_overload" ) & 2 ) + { + $this->_mbenc = mb_internal_encoding(); + mb_internal_encoding ( "latin1" ); + } + } + + /// leave mbstring workaround mode + function _MBPop () + { + if ( $this->_mbenc ) + mb_internal_encoding ( $this->_mbenc ); + } + + /// connect to searchd server + function _Connect ($allow_retry = true) + { + $errno = 0; + $errstr = ""; + if ( $this->_timeout<=0 ) + $fp = @fsockopen ( $this->_host, $this->_port, $errno, $errstr ); + else + $fp = @fsockopen ( $this->_host, $this->_port, $errno, $errstr, $this->_timeout ); + + if ( !$fp ) + { + $errstr = trim ( $errstr ); + $this->_error = "connection to {$this->_host}:{$this->_port} failed (errno=$errno, msg=$errstr)"; + return false; + } + + // check version + //list(,$v) = unpack ( "N*", fread ( $fp, 4 ) ); + $version_data = unpack ( "N*", fread ( $fp, 4 ) ); + if (!isset($version_data[1])) + { + // this should not happen, try to reconnect ONCE + if ($allow_retry) + { + return $this->_Connect(false); + } + else + { + $this->_error = "unexpected version data"; + return false; + } + } + $v = $version_data[1]; + $v = (int)$v; + if ( $v<1 ) + { + fclose ( $fp ); + $this->_error = "expected searchd protocol version 1+, got version '$v'"; + return false; + } + + // all ok, send my version + fwrite ( $fp, pack ( "N", 1 ) ); + return $fp; + } + + /// get and check response packet from searchd server + function _GetResponse ( $fp, $client_ver ) + { + $response = ""; + $len = 0; + + $header = fread ( $fp, 8 ); + if ( strlen($header)==8 ) + { + list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) ); + $left = $len; + while ( $left>0 && !feof($fp) ) + { + $chunk = fread ( $fp, $left ); + if ( $chunk ) + { + $response .= $chunk; + $left -= strlen($chunk); + } + } + } + fclose ( $fp ); + + // check response + $read = strlen ( $response ); + if ( !$response || $read!=$len ) + { + $this->_error = $len + ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)" + : "received zero-sized searchd response"; + return false; + } + + // check status + if ( $status==SEARCHD_WARNING ) + { + list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) ); + $this->_warning = substr ( $response, 4, $wlen ); + return substr ( $response, 4+$wlen ); + } + if ( $status==SEARCHD_ERROR ) + { + $this->_error = "searchd error: " . substr ( $response, 4 ); + return false; + } + if ( $status==SEARCHD_RETRY ) + { + $this->_error = "temporary searchd error: " . substr ( $response, 4 ); + return false; + } + if ( $status!=SEARCHD_OK ) + { + $this->_error = "unknown status code '$status'"; + return false; + } + + // check version + if ( $ver<$client_ver ) + { + $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work", + $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff ); + } + + return $response; + } + + ///////////////////////////////////////////////////////////////////////////// + // searching + ///////////////////////////////////////////////////////////////////////////// + + /// set offset and count into result set, + /// and optionally set max-matches and cutoff limits + function SetLimits ( $offset, $limit, $max=0, $cutoff=0 ) + { + assert ( is_int($offset) ); + assert ( is_int($limit) ); + assert ( $offset>=0 ); + assert ( $limit>0 ); + assert ( $max>=0 ); + $this->_offset = $offset; + $this->_limit = $limit; + if ( $max>0 ) + $this->_maxmatches = $max; + if ( $cutoff>0 ) + $this->_cutoff = $cutoff; + } + + /// set maximum query time, in milliseconds, per-index + /// integer, 0 means "do not limit" + function SetMaxQueryTime ( $max ) + { + assert ( is_int($max) ); + assert ( $max>=0 ); + $this->_maxquerytime = $max; + } + + /// set matching mode + function SetMatchMode ( $mode ) + { + assert ( $mode==SPH_MATCH_ALL + || $mode==SPH_MATCH_ANY + || $mode==SPH_MATCH_PHRASE + || $mode==SPH_MATCH_BOOLEAN + || $mode==SPH_MATCH_EXTENDED + || $mode==SPH_MATCH_FULLSCAN + || $mode==SPH_MATCH_EXTENDED2 ); + $this->_mode = $mode; + } + + /// set ranking mode + function SetRankingMode ( $ranker ) + { + assert ( $ranker==SPH_RANK_PROXIMITY_BM25 + || $ranker==SPH_RANK_BM25 + || $ranker==SPH_RANK_NONE + || $ranker==SPH_RANK_WORDCOUNT ); + $this->_ranker = $ranker; + } + + /// set matches sorting mode + function SetSortMode ( $mode, $sortby="" ) + { + assert ( + $mode==SPH_SORT_RELEVANCE || + $mode==SPH_SORT_ATTR_DESC || + $mode==SPH_SORT_ATTR_ASC || + $mode==SPH_SORT_TIME_SEGMENTS || + $mode==SPH_SORT_EXTENDED || + $mode==SPH_SORT_EXPR ); + assert ( is_string($sortby) ); + assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 ); + + $this->_sort = $mode; + $this->_sortby = $sortby; + } + + /// bind per-field weights by order + /// DEPRECATED; use SetFieldWeights() instead + function SetWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $weight ) + assert ( is_int($weight) ); + + $this->_weights = $weights; + } + + /// bind per-field weights by name + function SetFieldWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $name=>$weight ) + { + assert ( is_string($name) ); + assert ( is_int($weight) ); + } + $this->_fieldweights = $weights; + } + + /// bind per-index weights by name + function SetIndexWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $index=>$weight ) + { + assert ( is_string($index) ); + assert ( is_int($weight) ); + } + $this->_indexweights = $weights; + } + + /// set IDs range to match + /// only match records if document ID is beetwen $min and $max (inclusive) + function SetIDRange ( $min, $max ) + { + assert ( is_numeric($min) ); + assert ( is_numeric($max) ); + assert ( $min<=$max ); + $this->_min_id = $min; + $this->_max_id = $max; + } + + /// set values set filter + /// only match records where $attribute value is in given set + function SetFilter ( $attribute, $values, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_array($values) ); + assert ( count($values) ); + + if ( is_array($values) && count($values) ) + { + foreach ( $values as $value ) + assert ( is_numeric($value) ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values ); + } + } + + /// set range filter + /// only match records if $attribute value is beetwen $min and $max (inclusive) + function SetFilterRange ( $attribute, $min, $max, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_int($min) ); + assert ( is_int($max) ); + assert ( $min<=$max ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); + } + + /// set float range filter + /// only match records if $attribute value is beetwen $min and $max (inclusive) + function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_float($min) ); + assert ( is_float($max) ); + assert ( $min<=$max ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); + } + + /// setup anchor point for geosphere distance calculations + /// required to use @geodist in filters and sorting + /// latitude and longitude must be in radians + function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long ) + { + assert ( is_string($attrlat) ); + assert ( is_string($attrlong) ); + assert ( is_float($lat) ); + assert ( is_float($long) ); + + $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long ); + } + + /// set grouping attribute and function + function SetGroupBy ( $attribute, $func, $groupsort="@group desc" ) + { + assert ( is_string($attribute) ); + assert ( is_string($groupsort) ); + assert ( $func==SPH_GROUPBY_DAY + || $func==SPH_GROUPBY_WEEK + || $func==SPH_GROUPBY_MONTH + || $func==SPH_GROUPBY_YEAR + || $func==SPH_GROUPBY_ATTR + || $func==SPH_GROUPBY_ATTRPAIR ); + + $this->_groupby = $attribute; + $this->_groupfunc = $func; + $this->_groupsort = $groupsort; + } + + /// set count-distinct attribute for group-by queries + function SetGroupDistinct ( $attribute ) + { + assert ( is_string($attribute) ); + $this->_groupdistinct = $attribute; + } + + /// set distributed retries count and delay + function SetRetries ( $count, $delay=0 ) + { + assert ( is_int($count) && $count>=0 ); + assert ( is_int($delay) && $delay>=0 ); + $this->_retrycount = $count; + $this->_retrydelay = $delay; + } + + /// set result set format (hash or array; hash by default) + /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs + function SetArrayResult ( $arrayresult ) + { + assert ( is_bool($arrayresult) ); + $this->_arrayresult = $arrayresult; + } + + ////////////////////////////////////////////////////////////////////////////// + + /// clear all filters (for multi-queries) + function ResetFilters () + { + $this->_filters = array(); + $this->_anchor = array(); + } + + /// clear groupby settings (for multi-queries) + function ResetGroupBy () + { + $this->_groupby = ""; + $this->_groupfunc = SPH_GROUPBY_DAY; + $this->_groupsort = "@group desc"; + $this->_groupdistinct= ""; + } + + ////////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, run given search query through given indexes, + /// and return the search results + function Query ( $query, $index="*", $comment="" ) + { + assert ( empty($this->_reqs) ); + + $this->AddQuery ( $query, $index, $comment ); + $results = $this->RunQueries (); + $this->_reqs = array (); // just in case it failed too early + + if ( !is_array($results) ) + return false; // probably network error; error message should be already filled + + $this->_error = $results[0]["error"]; + $this->_warning = $results[0]["warning"]; + if ( $results[0]["status"]==SEARCHD_ERROR ) + return false; + else + return $results[0]; + } + + /// helper to pack floats in network byte order + function _PackFloat ( $f ) + { + $t1 = pack ( "f", $f ); // machine order + list(,$t2) = unpack ( "L*", $t1 ); // int in machine order + return pack ( "N", $t2 ); + } + + /// add query to multi-query batch + /// returns index into results array from RunQueries() call + function AddQuery ( $query, $index="*", $comment="" ) + { + // mbstring workaround + $this->_MBPush (); + + // build request + $req = pack ( "NNNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker, $this->_sort ); // mode and limits + $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby; + $req .= pack ( "N", strlen($query) ) . $query; // query itself + $req .= pack ( "N", count($this->_weights) ); // weights + foreach ( $this->_weights as $weight ) + $req .= pack ( "N", (int)$weight ); + $req .= pack ( "N", strlen($index) ) . $index; // indexes + $req .= pack ( "N", 1 ); // id64 range marker + $req .= sphPack64 ( $this->_min_id ) . sphPack64 ( $this->_max_id ); // id64 range + + // filters + $req .= pack ( "N", count($this->_filters) ); + foreach ( $this->_filters as $filter ) + { + $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"]; + $req .= pack ( "N", $filter["type"] ); + switch ( $filter["type"] ) + { + case SPH_FILTER_VALUES: + $req .= pack ( "N", count($filter["values"]) ); + foreach ( $filter["values"] as $value ) + $req .= pack ( "N", floatval($value) ); // this uberhack is to workaround 32bit signed int limit on x32 platforms + break; + + case SPH_FILTER_RANGE: + $req .= pack ( "NN", $filter["min"], $filter["max"] ); + break; + + case SPH_FILTER_FLOATRANGE: + $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] ); + break; + + default: + assert ( 0 && "internal error: unhandled filter type" ); + } + $req .= pack ( "N", $filter["exclude"] ); + } + + // group-by clause, max-matches count, group-sort clause, cutoff count + $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby; + $req .= pack ( "N", $this->_maxmatches ); + $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort; + $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay ); + $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct; + + // anchor point + if ( empty($this->_anchor) ) + { + $req .= pack ( "N", 0 ); + } else + { + $a =& $this->_anchor; + $req .= pack ( "N", 1 ); + $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"]; + $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"]; + $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] ); + } + + // per-index weights + $req .= pack ( "N", count($this->_indexweights) ); + foreach ( $this->_indexweights as $idx=>$weight ) + $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight ); + + // max query time + $req .= pack ( "N", $this->_maxquerytime ); + + // per-field weights + $req .= pack ( "N", count($this->_fieldweights) ); + foreach ( $this->_fieldweights as $field=>$weight ) + $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight ); + + // comment + $req .= pack ( "N", strlen($comment) ) . $comment; + + // mbstring workaround + $this->_MBPop (); + + // store request to requests array + $this->_reqs[] = $req; + return count($this->_reqs)-1; + } + + /// connect to searchd, run queries batch, and return an array of result sets + function RunQueries () + { + if ( empty($this->_reqs) ) + { + $this->_error = "no queries defined, issue AddQuery() first"; + return false; + } + + // mbstring workaround + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop (); + return false; + } + + //////////////////////////// + // send query, get response + //////////////////////////// + + $nreqs = count($this->_reqs); + $req = join ( "", $this->_reqs ); + $len = 4+strlen($req); + $req = pack ( "nnNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, $nreqs ) . $req; // add header + + fwrite ( $fp, $req, $len+8 ); + if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) )) + { + $this->_MBPop (); + return false; + } + + $this->_reqs = array (); + + ////////////////// + // parse response + ////////////////// + + $p = 0; // current position + $max = strlen($response); // max position for checks, to protect against broken responses + + $results = array (); + for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ ) + { + $results[] = array(); + $result =& $results[$ires]; + + $result["error"] = ""; + $result["warning"] = ""; + + // extract status + list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $result["status"] = $status; + if ( $status!=SEARCHD_OK ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $message = substr ( $response, $p, $len ); $p += $len; + + if ( $status==SEARCHD_WARNING ) + { + $result["warning"] = $message; + } else + { + $result["error"] = $message; + continue; + } + } + + // read schema + $fields = array (); + $attrs = array (); + + list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + while ( $nfields-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $fields[] = substr ( $response, $p, $len ); $p += $len; + } + $result["fields"] = $fields; + + list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + while ( $nattrs-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attr = substr ( $response, $p, $len ); $p += $len; + list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attrs[$attr] = $type; + } + $result["attrs"] = $attrs; + + // read match count + list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + + // read matches + $idx = -1; + while ( $count-->0 && $p<$max ) + { + // index into result array + $idx++; + + // parse document id and weight + if ( $id64 ) + { + $doc = sphUnpack64 ( substr ( $response, $p, 8 ) ); $p += 8; + list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + } else + { + list ( $doc, $weight ) = array_values ( unpack ( "N*N*", + substr ( $response, $p, 8 ) ) ); + $p += 8; + + if ( PHP_INT_SIZE>=8 ) + { + // x64 route, workaround broken unpack() in 5.2.2+ + if ( $doc<0 ) $doc += (1<<32); + } else + { + // x32 route, workaround php signed/unsigned braindamage + $doc = sprintf ( "%u", $doc ); + } + } + $weight = sprintf ( "%u", $weight ); + + // create match entry + if ( $this->_arrayresult ) + $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight ); + else + $result["matches"][$doc]["weight"] = $weight; + + // parse and create attributes + $attrvals = array (); + foreach ( $attrs as $attr=>$type ) + { + // handle floats + if ( $type==SPH_ATTR_FLOAT ) + { + list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); + $attrvals[$attr] = $fval; + continue; + } + + // handle everything else as unsigned ints + list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + if ( $type & SPH_ATTR_MULTI ) + { + $attrvals[$attr] = array (); + $nvalues = $val; + while ( $nvalues-->0 && $p<$max ) + { + list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attrvals[$attr][] = sprintf ( "%u", $val ); + } + } else + { + $attrvals[$attr] = sprintf ( "%u", $val ); + } + } + + if ( $this->_arrayresult ) + $result["matches"][$idx]["attrs"] = $attrvals; + else + $result["matches"][$doc]["attrs"] = $attrvals; + } + + list ( $total, $total_found, $msecs, $words ) = + array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) ); + $result["total"] = sprintf ( "%u", $total ); + $result["total_found"] = sprintf ( "%u", $total_found ); + $result["time"] = sprintf ( "%.3f", $msecs/1000 ); + $p += 16; + + while ( $words-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $word = substr ( $response, $p, $len ); $p += $len; + list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; + $result["words"][$word] = array ( + "docs"=>sprintf ( "%u", $docs ), + "hits"=>sprintf ( "%u", $hits ) ); + } + } + + $this->_MBPop (); + return $results; + } + + ///////////////////////////////////////////////////////////////////////////// + // excerpts generation + ///////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, and generate exceprts (snippets) + /// of given documents for given query. returns false on failure, + /// an array of snippets on success + function BuildExcerpts ( $docs, $index, $words, $opts=array() ) + { + assert ( is_array($docs) ); + assert ( is_string($index) ); + assert ( is_string($words) ); + assert ( is_array($opts) ); + + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + ///////////////// + // fixup options + ///////////////// + + if ( !isset($opts["before_match"]) ) $opts["before_match"] = ""; + if ( !isset($opts["after_match"]) ) $opts["after_match"] = ""; + if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... "; + if ( !isset($opts["limit"]) ) $opts["limit"] = 256; + if ( !isset($opts["around"]) ) $opts["around"] = 5; + if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false; + if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false; + if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false; + if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false; + + ///////////////// + // build request + ///////////////// + + // v.1.0 req + $flags = 1; // remove spaces + if ( $opts["exact_phrase"] ) $flags |= 2; + if ( $opts["single_passage"] ) $flags |= 4; + if ( $opts["use_boundaries"] ) $flags |= 8; + if ( $opts["weight_order"] ) $flags |= 16; + $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags + $req .= pack ( "N", strlen($index) ) . $index; // req index + $req .= pack ( "N", strlen($words) ) . $words; // req words + + // options + $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"]; + $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"]; + $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"]; + $req .= pack ( "N", (int)$opts["limit"] ); + $req .= pack ( "N", (int)$opts["around"] ); + + // documents + $req .= pack ( "N", count($docs) ); + foreach ( $docs as $doc ) + { + assert ( is_string($doc) ); + $req .= pack ( "N", strlen($doc) ) . $doc; + } + + //////////////////////////// + // send query, get response + //////////////////////////// + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header + $wrote = fwrite ( $fp, $req, $len+8 ); + if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) )) + { + $this->_MBPop (); + return false; + } + + ////////////////// + // parse response + ////////////////// + + $pos = 0; + $res = array (); + $rlen = strlen($response); + for ( $i=0; $i $rlen ) + { + $this->_error = "incomplete reply"; + $this->_MBPop (); + return false; + } + $res[] = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + } + + $this->_MBPop (); + return $res; + } + + + ///////////////////////////////////////////////////////////////////////////// + // keyword generation + ///////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, and generate keyword list for a given query + /// returns false on failure, + /// an array of words on success + function BuildKeywords ( $query, $index, $hits ) + { + assert ( is_string($query) ); + assert ( is_string($index) ); + assert ( is_bool($hits) ); + + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + ///////////////// + // build request + ///////////////// + + // v.1.0 req + $req = pack ( "N", strlen($query) ) . $query; // req query + $req .= pack ( "N", strlen($index) ) . $index; // req index + $req .= pack ( "N", (int)$hits ); + + //////////////////////////// + // send query, get response + //////////////////////////// + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header + $wrote = fwrite ( $fp, $req, $len+8 ); + if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) )) + { + $this->_MBPop (); + return false; + } + + ////////////////// + // parse response + ////////////////// + + $pos = 0; + $res = array (); + $rlen = strlen($response); + list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) ); + $pos += 4; + for ( $i=0; $i<$nwords; $i++ ) + { + list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; + $tokenized = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + + list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; + $normalized = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + + $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized ); + + if ( $hits ) + { + list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) ); + $pos += 8; + $res [$i]["docs"] = $ndocs; + $res [$i]["hits"] = $nhits; + } + + if ( $pos > $rlen ) + { + $this->_error = "incomplete reply"; + $this->_MBPop (); + return false; + } + } + + $this->_MBPop (); + return $res; + } + + function EscapeString ( $string ) + { + $from = array ( '(',')','|','-','!','@','~','"','&', '/' ); + $to = array ( '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/' ); + + return str_replace ( $from, $to, $string ); + } + + ///////////////////////////////////////////////////////////////////////////// + // attribute updates + ///////////////////////////////////////////////////////////////////////////// + + /// update given attribute values on given documents in given indexes + /// returns amount of updated documents (0 or more) on success, or -1 on failure + function UpdateAttributes ( $index, $attrs, $values ) + { + // verify everything + assert ( is_string($index) ); + + assert ( is_array($attrs) ); + foreach ( $attrs as $attr ) + assert ( is_string($attr) ); + + assert ( is_array($values) ); + foreach ( $values as $id=>$entry ) + { + assert ( is_numeric($id) ); + assert ( is_array($entry) ); + assert ( count($entry)==count($attrs) ); + foreach ( $entry as $v ) + assert ( is_int($v) ); + } + + // build request + $req = pack ( "N", strlen($index) ) . $index; + + $req .= pack ( "N", count($attrs) ); + foreach ( $attrs as $attr ) + $req .= pack ( "N", strlen($attr) ) . $attr; + + $req .= pack ( "N", count($values) ); + foreach ( $values as $id=>$entry ) + { + $req .= sphPack64 ( $id ); + foreach ( $entry as $v ) + $req .= pack ( "N", $v ); + } + + // mbstring workaround + $this->_MBPush (); + + // connect, send query, get response + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop (); + return -1; + } + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header + fwrite ( $fp, $req, $len+8 ); + + if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) )) + { + $this->_MBPop (); + return -1; + } + + // parse response + list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) ); + $this->_MBPop (); + return $updated; + } +} + +// +// $Id$ +// \ No newline at end of file diff --git a/phpBB/language/en/mods/fulltext_sphinx.php b/phpBB/language/en/mods/fulltext_sphinx.php new file mode 100644 index 0000000000..e06328afc8 --- /dev/null +++ b/phpBB/language/en/mods/fulltext_sphinx.php @@ -0,0 +1,65 @@ + 'Automatically configure Sphinx', + 'FULLTEXT_SPHINX_AUTOCONF_EXPLAIN' => 'This is the easiest way to install Sphinx, just select the settings here and a config file will be written for you. This requires write permissions on the configuration folder.', + 'FULLTEXT_SPHINX_AUTORUN' => 'Automatically run Sphinx', + 'FULLTEXT_SPHINX_AUTORUN_EXPLAIN' => 'This is the easiest way to run Sphinx. Select the paths in this dialogue and the Sphinx daemon will be started and stopped as needed. You can also create an index from the ACP. If your PHP installation forbids the use of exec you can disable this and run Sphinx manually.', + 'FULLTEXT_SPHINX_BIN_PATH' => 'Path to executables directory', + 'FULLTEXT_SPHINX_BIN_PATH_EXPLAIN' => 'Skip if autorun is disabled. If this path could not be determined automatically you have to enter the path to the directory in which the sphinx executables indexer and searchd reside.', + 'FULLTEXT_SPHINX_CONFIG_PATH' => 'Path to configuration directory', + 'FULLTEXT_SPHINX_CONFIG_PATH_EXPLAIN' => 'Skip if autoconf is disabled. You should create this config directory outside the web accessable directories. It has to be writable by the user as which your webserver is running (often www-data or nobody).', + 'FULLTEXT_SPHINX_CONFIGURE_FIRST' => 'Before you create an index you have to enable and configure sphinx under GENERAL -> SERVER CONFIGURATION -> Search settings.', + 'FULLTEXT_SPHINX_CONFIGURE_BEFORE' => 'Configure the following settings BEFORE activating Sphinx', + 'FULLTEXT_SPHINX_CONFIGURE_AFTER' => 'The following settings do not have to be configured before activating Sphinx', + 'FULLTEXT_SPHINX_DATA_PATH' => 'Path to data directory', + 'FULLTEXT_SPHINX_DATA_PATH_EXPLAIN' => 'Skip if autorun is disabled. You should create this directory outside the web accessable directories. It has to be writable by the user as which your webserver is running (often www-data or nobody). It will be used to store the indexes and log files.', + 'FULLTEXT_SPHINX_DELTA_POSTS' => 'Number of posts in frequently updated delta index', + 'FULLTEXT_SPHINX_DIRECTORY_NOT_FOUND' => 'The directory %s does not exist. Please correct your path settings.', + 'FULLTEXT_SPHINX_FILE_NOT_EXECUTABLE' => 'The file %s is not executable for the webserver.', + 'FULLTEXT_SPHINX_FILE_NOT_FOUND' => 'The file %s does not exist. Please correct your path settings.', + 'FULLTEXT_SPHINX_FILE_NOT_WRITABLE' => 'The file %s cannot be written by the webserver.', + 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT' => 'Indexer memory limit', + 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN' => 'This number should at all times be lower than the RAM available on your machine. If you experience periodic performance problems this might be due to the indexer consuming too many resources. It might help to lower the amount of memory available to the indexer.', + 'FULLTEXT_SPHINX_LAST_SEARCHES' => 'Recent search queries', + 'FULLTEXT_SPHINX_MAIN_POSTS' => 'Number of posts in main index', + 'FULLTEXT_SPHINX_PORT' => 'Sphinx search deamon port', + 'FULLTEXT_SPHINX_PORT_EXPLAIN' => 'Port on which the sphinx search deamon on localhost listens. Leave empty to use the default 3312', + 'FULLTEXT_SPHINX_REQUIRES_EXEC' => 'The sphinx plugin for phpBB requires PHP’s exec function which is disabled on your system.', + 'FULLTEXT_SPHINX_UNCONFIGURED' => 'Please set all necessary options in the "Fulltext Sphinx" section of the previous page before you try to activate the sphinx plugin.', + 'FULLTEXT_SPHINX_WRONG_DATABASE' => 'The sphinx plugin for phpBB currently only supports MySQL', + 'FULLTEXT_SPHINX_STOPWORDS_FILE' => 'Stopwords activated', + 'FULLTEXT_SPHINX_STOPWORDS_FILE_EXPLAIN' => 'This setting only works with autoconf enabled. You can place a file called sphinx_stopwords.txt containing one word in each line in your config directory. If this file is present these words will be excluded from the indexing process.', +)); + +?> \ No newline at end of file From fcf0d04b20f1c862117a8ab962d692bd2b8b074f Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Wed, 9 May 2012 19:13:36 +0530 Subject: [PATCH 0229/1142] [feature/sphinx-fulltext-search] minor changes some minor code changes to make it working against current develop and comply with other search backend coding convetions. PHPBB3-10946 --- phpBB/includes/search/fulltext_sphinx.php | 32 +++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index e0c467df93..ef357970a0 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -18,33 +18,27 @@ if (!defined('IN_PHPBB')) /** * @ignore */ +/** +* This statement is necessary as this file is sometimes included from within a +* function and the variables used are in global space. +*/ +global $phpbb_root_path, $phpEx, $table_prefix; require($phpbb_root_path . "includes/sphinxapi-0.9.8." . $phpEx); define('INDEXER_NAME', 'indexer'); define('SEARCHD_NAME', 'searchd'); -define('SPHINX_TABLE', table_prefix() . 'sphinx'); +define('SPHINX_TABLE', $table_prefix . 'sphinx'); define('MAX_MATCHES', 20000); define('CONNECT_RETRIES', 3); define('CONNECT_WAIT_TIME', 300); -/** -* Returns the global table prefix -* This function is necessary as this file is sometimes included from within a -* function and table_prefix is in global space. -*/ -function table_prefix() -{ - global $table_prefix; - return $table_prefix; -} - /** * fulltext_sphinx * Fulltext search based on the sphinx search deamon * @package search */ -class fulltext_sphinx +class phpbb_search_fulltext_sphinx { var $stats = array(); var $word_length = array(); @@ -53,7 +47,7 @@ class fulltext_sphinx var $common_words = array(); var $id; - function fulltext_sphinx(&$error) + public function __construct(&$error) { global $config; @@ -82,6 +76,16 @@ class fulltext_sphinx $error = false; } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + public function get_name() + { + return 'Sphinx Fulltext'; + } /** * Checks permissions and paths, if everything is correct it generates the config file From 99d4660df68d71ea56cccb150ae858c1dd7575b8 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Wed, 20 Jun 2012 05:11:53 +0530 Subject: [PATCH 0230/1142] [feature/sphinx-fulltext-search] update config file Sphinx config file updated according to new documentation. PHPBB3-10946 --- phpBB/includes/search/fulltext_sphinx.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index ef357970a0..6c5092f4aa 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -295,7 +295,7 @@ class phpbb_search_fulltext_sphinx array('stopwords', (file_exists($config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt') && $config['fulltext_sphinx_stopwords']) ? $config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt' : ''), array('min_word_len', '2'), array('charset_type', 'utf-8'), - array('charset_table', 'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+ß410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'), + array('charset_table', 'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'), array('min_prefix_len', '0'), array('min_infix_len', '0'), ), @@ -307,7 +307,8 @@ class phpbb_search_fulltext_sphinx array('mem_limit', $config['fulltext_sphinx_indexer_mem_limit'] . 'M'), ), 'searchd' => array( - array('address' , '127.0.0.1'), + array('compat_sphinxql_magics' , '0'), + array('listen' , '127.0.0.1'), array('port', ($config['fulltext_sphinx_port']) ? $config['fulltext_sphinx_port'] : '3312'), array('log', $config['fulltext_sphinx_data_path'] . "log/searchd.log"), array('query_log', $config['fulltext_sphinx_data_path'] . "log/sphinx-query.log"), From 8d76bc45ee19186f40dd3b459a9bd33e5e4c23d9 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 26 Jun 2012 02:35:36 +0530 Subject: [PATCH 0231/1142] [feature/sphinx-fulltext-search] minor fixes in formatting Add a newline at the end of files. Update License information in package docbloc. PHPBB3-10946 --- phpBB/includes/functions_sphinx.php | 5 ++--- phpBB/includes/search/fulltext_sphinx.php | 5 +---- phpBB/includes/sphinxapi-0.9.8.php | 2 +- phpBB/language/en/mods/fulltext_sphinx.php | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/phpBB/includes/functions_sphinx.php b/phpBB/includes/functions_sphinx.php index 976f93f77c..0f83f8cfb5 100644 --- a/phpBB/includes/functions_sphinx.php +++ b/phpBB/includes/functions_sphinx.php @@ -2,9 +2,8 @@ /** * * @package search -* @version $Id$ * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 * */ @@ -504,4 +503,4 @@ class sphinx_config_comment } } -?> \ No newline at end of file +?> diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 6c5092f4aa..9ae6438af2 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -2,9 +2,8 @@ /** * * @package search -* @version $Id$ * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 * */ @@ -1167,5 +1166,3 @@ function sphinx_read_last_lines($file, $amount) return $string; } - -?> \ No newline at end of file diff --git a/phpBB/includes/sphinxapi-0.9.8.php b/phpBB/includes/sphinxapi-0.9.8.php index 6a7ea17760..816895d464 100644 --- a/phpBB/includes/sphinxapi-0.9.8.php +++ b/phpBB/includes/sphinxapi-0.9.8.php @@ -1199,4 +1199,4 @@ class SphinxClient // // $Id$ -// \ No newline at end of file +// diff --git a/phpBB/language/en/mods/fulltext_sphinx.php b/phpBB/language/en/mods/fulltext_sphinx.php index e06328afc8..f3fd68aa62 100644 --- a/phpBB/language/en/mods/fulltext_sphinx.php +++ b/phpBB/language/en/mods/fulltext_sphinx.php @@ -62,4 +62,4 @@ $lang = array_merge($lang, array( 'FULLTEXT_SPHINX_STOPWORDS_FILE_EXPLAIN' => 'This setting only works with autoconf enabled. You can place a file called sphinx_stopwords.txt containing one word in each line in your config directory. If this file is present these words will be excluded from the indexing process.', )); -?> \ No newline at end of file +?> From a3b2caf8416c687306b3c2e83b2fdc6e8708cce0 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Mon, 25 Jun 2012 00:03:46 +0530 Subject: [PATCH 0232/1142] [feature/sphinx-fulltext-search] include sample sphinx.conf in docs PHPBB3-10946 --- phpBB/docs/sphinx.sample.conf | 96 +++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 phpBB/docs/sphinx.sample.conf diff --git a/phpBB/docs/sphinx.sample.conf b/phpBB/docs/sphinx.sample.conf new file mode 100644 index 0000000000..d7e59a11fc --- /dev/null +++ b/phpBB/docs/sphinx.sample.conf @@ -0,0 +1,96 @@ +source source_phpbb_{AVATAR_SALT}_main +{ + type = mysql + sql_host = localhost + sql_user = username + sql_pass = password + sql_db = db_name + sql_port = 3306 #optional, default is 3306 + sql_query_range = SELECT MIN(post_id), MAX(post_id) FROM phpbb_posts + sql_range_step = 5000 + sql_query = SELECT \ + p.post_id AS id, \ + p.forum_id, \ + p.topic_id, \ + p.poster_id, \ + IF(p.post_id = t.topic_first_post_id, 1, 0) as topic_first_post, \ + p.post_time, \ + p.post_subject, \ + p.post_subject as title, \ + p.post_text as data, \ + t.topic_last_post_time, \ + 0 as deleted \ + FROM phpbb_posts p, phpbb_topics t \ + WHERE \ + p.topic_id = t.topic_id \ + AND p.post_id >= $start AND p.post_id <= $end + sql_query_post = + sql_query_post_index = REPLACE INTO phpbb_sphinx ( counter_id, max_doc_id ) VALUES ( 1, $maxid ) + sql_query_info = SELECT * FROM phpbb_posts WHERE post_id = $id + sql_query_pre = SET NAMES utf8 + sql_query_pre = REPLACE INTO phpbb_sphinx SELECT 1, MAX(post_id) FROM phpbb_posts + sql_attr_uint = forum_id + sql_attr_uint = topic_id + sql_attr_uint = poster_id + sql_attr_bool = topic_first_post + sql_attr_bool = deleted + sql_attr_timestamp = post_time + sql_attr_timestamp = topic_last_post_time + sql_attr_str2ordinal = post_subject +} +source source_phpbb_{AVATAR_SALT}_delta : source_phpbb_{AVATAR_SALT}_main +{ + sql_query_range = + sql_range_step = + sql_query = SELECT \ + p.post_id AS id, \ + p.forum_id, \ + p.topic_id, \ + p.poster_id, \ + IF(p.post_id = t.topic_first_post_id, 1, 0) as topic_first_post, \ + p.post_time, \ + p.post_subject, \ + p.post_subject as title, \ + p.post_text as data, \ + t.topic_last_post_time, \ + 0 as deleted \ + FROM phpbb_posts p, phpbb_topics t \ + WHERE \ + p.topic_id = t.topic_id \ + AND p.post_id >= ( SELECT max_doc_id FROM phpbb_sphinx WHERE counter_id=1 ) + sql_query_pre = +} +index index_phpbb_{AVATAR_SALT}_main +{ + path = {DATA_PATH}/index_phpbb_{AVATAR_SALT}_main + source = source_phpbb_{AVATAR_SALT}_main + docinfo = extern + morphology = none + stopwords = + min_word_len = 2 + charset_type = utf-8 + charset_table = U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF + min_prefix_len = 0 + min_infix_len = 0 +} +index index_phpbb_{AVATAR_SALT}_delta : index_phpbb_{AVATAR_SALT}_main +{ + path = {DATA_PATH}/index_phpbb_{AVATAR_SALT}_delta + source = source_phpbb_{AVATAR_SALT}_delta +} +indexer +{ + mem_limit = 512M +} +searchd +{ + compat_sphinxql_magics = 0 + listen = 127.0.0.1 + port = 3312 + log = {DATA_PATH}/log/searchd.log + query_log = {DATA_PATH}/log/sphinx-query.log + read_timeout = 5 + max_children = 30 + pid_file = {DATA_PATH}/searchd.pid + max_matches = 20000 +} From 455a35d8361c93657874e140a2ad5b2e5c267757 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 26 Jun 2012 03:56:03 +0530 Subject: [PATCH 0233/1142] [feature/sphinx-fulltext-search] temporary commit to pull out sphinx-api also need to add the latest sphinx api instead of this. PHPBB3-10946 --- phpBB/includes/sphinxapi-0.9.8.php | 1202 ---------------------------- 1 file changed, 1202 deletions(-) delete mode 100644 phpBB/includes/sphinxapi-0.9.8.php diff --git a/phpBB/includes/sphinxapi-0.9.8.php b/phpBB/includes/sphinxapi-0.9.8.php deleted file mode 100644 index 816895d464..0000000000 --- a/phpBB/includes/sphinxapi-0.9.8.php +++ /dev/null @@ -1,1202 +0,0 @@ -=8 ) - { - $i = (int)$v; - return pack ( "NN", $i>>32, $i&((1<<32)-1) ); - } - - // x32 route, bcmath - $x = "4294967296"; - if ( function_exists("bcmul") ) - { - $h = bcdiv ( $v, $x, 0 ); - $l = bcmod ( $v, $x ); - return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit - } - - // x32 route, 15 or less decimal digits - // we can use float, because its actually double and has 52 precision bits - if ( strlen($v)<=15 ) - { - $f = (float)$v; - $h = (int)($f/$x); - $l = (int)($f-$x*$h); - return pack ( "NN", $h, $l ); - } - - // x32 route, 16 or more decimal digits - // well, let me know if you *really* need this - die ( "INTERNAL ERROR: packing more than 15-digit numeric on 32-bit PHP is not implemented yet (contact support)" ); -} - - -/// portably unpack 64 unsigned bits, network order to numeric -function sphUnpack64 ( $v ) -{ - list($h,$l) = array_values ( unpack ( "N*N*", $v ) ); - - // x64 route - if ( PHP_INT_SIZE>=8 ) - { - if ( $h<0 ) $h += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again - if ( $l<0 ) $l += (1<<32); - return ($h<<32) + $l; - } - - // x32 route - $h = sprintf ( "%u", $h ); - $l = sprintf ( "%u", $l ); - $x = "4294967296"; - - // bcmath - if ( function_exists("bcmul") ) - return bcadd ( $l, bcmul ( $x, $h ) ); - - // no bcmath, 15 or less decimal digits - // we can use float, because its actually double and has 52 precision bits - if ( $h<1048576 ) - { - $f = ((float)$h)*$x + (float)$l; - return sprintf ( "%.0f", $f ); // builtin conversion is only about 39-40 bits precise! - } - - // x32 route, 16 or more decimal digits - // well, let me know if you *really* need this - die ( "INTERNAL ERROR: unpacking more than 15-digit numeric on 32-bit PHP is not implemented yet (contact support)" ); -} - - -/// sphinx searchd client class -class SphinxClient -{ - var $_host; ///< searchd host (default is "localhost") - var $_port; ///< searchd port (default is 3312) - var $_offset; ///< how many records to seek from result-set start (default is 0) - var $_limit; ///< how many records to return from result-set starting at offset (default is 20) - var $_mode; ///< query matching mode (default is SPH_MATCH_ALL) - var $_weights; ///< per-field weights (default is 1 for all fields) - var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE) - var $_sortby; ///< attribute to sort by (defualt is "") - var $_min_id; ///< min ID to match (default is 0, which means no limit) - var $_max_id; ///< max ID to match (default is 0, which means no limit) - var $_filters; ///< search filters - var $_groupby; ///< group-by attribute name - var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with) - var $_groupsort; ///< group-by sorting clause (to sort groups in result set with) - var $_groupdistinct;///< group-by count-distinct attribute - var $_maxmatches; ///< max matches to retrieve - var $_cutoff; ///< cutoff to stop searching at (default is 0) - var $_retrycount; ///< distributed retries count - var $_retrydelay; ///< distributed retries delay - var $_anchor; ///< geographical anchor point - var $_indexweights; ///< per-index weights - var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25) - var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit) - var $_fieldweights; ///< per-field-name weights - - var $_error; ///< last error message - var $_warning; ///< last warning message - - var $_reqs; ///< requests array for multi-query - var $_mbenc; ///< stored mbstring encoding - var $_arrayresult; ///< whether $result["matches"] should be a hash or an array - var $_timeout; ///< connect timeout - - ///////////////////////////////////////////////////////////////////////////// - // common stuff - ///////////////////////////////////////////////////////////////////////////// - - /// create a new client object and fill defaults - function SphinxClient () - { - // per-client-object settings - $this->_host = "localhost"; - $this->_port = 3312; - - // per-query settings - $this->_offset = 0; - $this->_limit = 20; - $this->_mode = SPH_MATCH_ALL; - $this->_weights = array (); - $this->_sort = SPH_SORT_RELEVANCE; - $this->_sortby = ""; - $this->_min_id = 0; - $this->_max_id = 0; - $this->_filters = array (); - $this->_groupby = ""; - $this->_groupfunc = SPH_GROUPBY_DAY; - $this->_groupsort = "@group desc"; - $this->_groupdistinct= ""; - $this->_maxmatches = 1000; - $this->_cutoff = 0; - $this->_retrycount = 0; - $this->_retrydelay = 0; - $this->_anchor = array (); - $this->_indexweights= array (); - $this->_ranker = SPH_RANK_PROXIMITY_BM25; - $this->_maxquerytime= 0; - $this->_fieldweights= array(); - - $this->_error = ""; // per-reply fields (for single-query case) - $this->_warning = ""; - $this->_reqs = array (); // requests storage (for multi-query case) - $this->_mbenc = ""; - $this->_arrayresult = false; - $this->_timeout = 0; - } - - /// get last error message (string) - function GetLastError () - { - return $this->_error; - } - - /// get last warning message (string) - function GetLastWarning () - { - return $this->_warning; - } - - /// set searchd host name (string) and port (integer) - function SetServer ( $host, $port ) - { - assert ( is_string($host) ); - assert ( is_int($port) ); - $this->_host = $host; - $this->_port = $port; - } - - /// set server connection timeout (0 to remove) - function SetConnectTimeout ( $timeout ) - { - assert ( is_numeric($timeout) ); - $this->_timeout = $timeout; - } - - ///////////////////////////////////////////////////////////////////////////// - - /// enter mbstring workaround mode - function _MBPush () - { - $this->_mbenc = ""; - if ( ini_get ( "mbstring.func_overload" ) & 2 ) - { - $this->_mbenc = mb_internal_encoding(); - mb_internal_encoding ( "latin1" ); - } - } - - /// leave mbstring workaround mode - function _MBPop () - { - if ( $this->_mbenc ) - mb_internal_encoding ( $this->_mbenc ); - } - - /// connect to searchd server - function _Connect ($allow_retry = true) - { - $errno = 0; - $errstr = ""; - if ( $this->_timeout<=0 ) - $fp = @fsockopen ( $this->_host, $this->_port, $errno, $errstr ); - else - $fp = @fsockopen ( $this->_host, $this->_port, $errno, $errstr, $this->_timeout ); - - if ( !$fp ) - { - $errstr = trim ( $errstr ); - $this->_error = "connection to {$this->_host}:{$this->_port} failed (errno=$errno, msg=$errstr)"; - return false; - } - - // check version - //list(,$v) = unpack ( "N*", fread ( $fp, 4 ) ); - $version_data = unpack ( "N*", fread ( $fp, 4 ) ); - if (!isset($version_data[1])) - { - // this should not happen, try to reconnect ONCE - if ($allow_retry) - { - return $this->_Connect(false); - } - else - { - $this->_error = "unexpected version data"; - return false; - } - } - $v = $version_data[1]; - $v = (int)$v; - if ( $v<1 ) - { - fclose ( $fp ); - $this->_error = "expected searchd protocol version 1+, got version '$v'"; - return false; - } - - // all ok, send my version - fwrite ( $fp, pack ( "N", 1 ) ); - return $fp; - } - - /// get and check response packet from searchd server - function _GetResponse ( $fp, $client_ver ) - { - $response = ""; - $len = 0; - - $header = fread ( $fp, 8 ); - if ( strlen($header)==8 ) - { - list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) ); - $left = $len; - while ( $left>0 && !feof($fp) ) - { - $chunk = fread ( $fp, $left ); - if ( $chunk ) - { - $response .= $chunk; - $left -= strlen($chunk); - } - } - } - fclose ( $fp ); - - // check response - $read = strlen ( $response ); - if ( !$response || $read!=$len ) - { - $this->_error = $len - ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)" - : "received zero-sized searchd response"; - return false; - } - - // check status - if ( $status==SEARCHD_WARNING ) - { - list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) ); - $this->_warning = substr ( $response, 4, $wlen ); - return substr ( $response, 4+$wlen ); - } - if ( $status==SEARCHD_ERROR ) - { - $this->_error = "searchd error: " . substr ( $response, 4 ); - return false; - } - if ( $status==SEARCHD_RETRY ) - { - $this->_error = "temporary searchd error: " . substr ( $response, 4 ); - return false; - } - if ( $status!=SEARCHD_OK ) - { - $this->_error = "unknown status code '$status'"; - return false; - } - - // check version - if ( $ver<$client_ver ) - { - $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work", - $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff ); - } - - return $response; - } - - ///////////////////////////////////////////////////////////////////////////// - // searching - ///////////////////////////////////////////////////////////////////////////// - - /// set offset and count into result set, - /// and optionally set max-matches and cutoff limits - function SetLimits ( $offset, $limit, $max=0, $cutoff=0 ) - { - assert ( is_int($offset) ); - assert ( is_int($limit) ); - assert ( $offset>=0 ); - assert ( $limit>0 ); - assert ( $max>=0 ); - $this->_offset = $offset; - $this->_limit = $limit; - if ( $max>0 ) - $this->_maxmatches = $max; - if ( $cutoff>0 ) - $this->_cutoff = $cutoff; - } - - /// set maximum query time, in milliseconds, per-index - /// integer, 0 means "do not limit" - function SetMaxQueryTime ( $max ) - { - assert ( is_int($max) ); - assert ( $max>=0 ); - $this->_maxquerytime = $max; - } - - /// set matching mode - function SetMatchMode ( $mode ) - { - assert ( $mode==SPH_MATCH_ALL - || $mode==SPH_MATCH_ANY - || $mode==SPH_MATCH_PHRASE - || $mode==SPH_MATCH_BOOLEAN - || $mode==SPH_MATCH_EXTENDED - || $mode==SPH_MATCH_FULLSCAN - || $mode==SPH_MATCH_EXTENDED2 ); - $this->_mode = $mode; - } - - /// set ranking mode - function SetRankingMode ( $ranker ) - { - assert ( $ranker==SPH_RANK_PROXIMITY_BM25 - || $ranker==SPH_RANK_BM25 - || $ranker==SPH_RANK_NONE - || $ranker==SPH_RANK_WORDCOUNT ); - $this->_ranker = $ranker; - } - - /// set matches sorting mode - function SetSortMode ( $mode, $sortby="" ) - { - assert ( - $mode==SPH_SORT_RELEVANCE || - $mode==SPH_SORT_ATTR_DESC || - $mode==SPH_SORT_ATTR_ASC || - $mode==SPH_SORT_TIME_SEGMENTS || - $mode==SPH_SORT_EXTENDED || - $mode==SPH_SORT_EXPR ); - assert ( is_string($sortby) ); - assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 ); - - $this->_sort = $mode; - $this->_sortby = $sortby; - } - - /// bind per-field weights by order - /// DEPRECATED; use SetFieldWeights() instead - function SetWeights ( $weights ) - { - assert ( is_array($weights) ); - foreach ( $weights as $weight ) - assert ( is_int($weight) ); - - $this->_weights = $weights; - } - - /// bind per-field weights by name - function SetFieldWeights ( $weights ) - { - assert ( is_array($weights) ); - foreach ( $weights as $name=>$weight ) - { - assert ( is_string($name) ); - assert ( is_int($weight) ); - } - $this->_fieldweights = $weights; - } - - /// bind per-index weights by name - function SetIndexWeights ( $weights ) - { - assert ( is_array($weights) ); - foreach ( $weights as $index=>$weight ) - { - assert ( is_string($index) ); - assert ( is_int($weight) ); - } - $this->_indexweights = $weights; - } - - /// set IDs range to match - /// only match records if document ID is beetwen $min and $max (inclusive) - function SetIDRange ( $min, $max ) - { - assert ( is_numeric($min) ); - assert ( is_numeric($max) ); - assert ( $min<=$max ); - $this->_min_id = $min; - $this->_max_id = $max; - } - - /// set values set filter - /// only match records where $attribute value is in given set - function SetFilter ( $attribute, $values, $exclude=false ) - { - assert ( is_string($attribute) ); - assert ( is_array($values) ); - assert ( count($values) ); - - if ( is_array($values) && count($values) ) - { - foreach ( $values as $value ) - assert ( is_numeric($value) ); - - $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values ); - } - } - - /// set range filter - /// only match records if $attribute value is beetwen $min and $max (inclusive) - function SetFilterRange ( $attribute, $min, $max, $exclude=false ) - { - assert ( is_string($attribute) ); - assert ( is_int($min) ); - assert ( is_int($max) ); - assert ( $min<=$max ); - - $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); - } - - /// set float range filter - /// only match records if $attribute value is beetwen $min and $max (inclusive) - function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false ) - { - assert ( is_string($attribute) ); - assert ( is_float($min) ); - assert ( is_float($max) ); - assert ( $min<=$max ); - - $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); - } - - /// setup anchor point for geosphere distance calculations - /// required to use @geodist in filters and sorting - /// latitude and longitude must be in radians - function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long ) - { - assert ( is_string($attrlat) ); - assert ( is_string($attrlong) ); - assert ( is_float($lat) ); - assert ( is_float($long) ); - - $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long ); - } - - /// set grouping attribute and function - function SetGroupBy ( $attribute, $func, $groupsort="@group desc" ) - { - assert ( is_string($attribute) ); - assert ( is_string($groupsort) ); - assert ( $func==SPH_GROUPBY_DAY - || $func==SPH_GROUPBY_WEEK - || $func==SPH_GROUPBY_MONTH - || $func==SPH_GROUPBY_YEAR - || $func==SPH_GROUPBY_ATTR - || $func==SPH_GROUPBY_ATTRPAIR ); - - $this->_groupby = $attribute; - $this->_groupfunc = $func; - $this->_groupsort = $groupsort; - } - - /// set count-distinct attribute for group-by queries - function SetGroupDistinct ( $attribute ) - { - assert ( is_string($attribute) ); - $this->_groupdistinct = $attribute; - } - - /// set distributed retries count and delay - function SetRetries ( $count, $delay=0 ) - { - assert ( is_int($count) && $count>=0 ); - assert ( is_int($delay) && $delay>=0 ); - $this->_retrycount = $count; - $this->_retrydelay = $delay; - } - - /// set result set format (hash or array; hash by default) - /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs - function SetArrayResult ( $arrayresult ) - { - assert ( is_bool($arrayresult) ); - $this->_arrayresult = $arrayresult; - } - - ////////////////////////////////////////////////////////////////////////////// - - /// clear all filters (for multi-queries) - function ResetFilters () - { - $this->_filters = array(); - $this->_anchor = array(); - } - - /// clear groupby settings (for multi-queries) - function ResetGroupBy () - { - $this->_groupby = ""; - $this->_groupfunc = SPH_GROUPBY_DAY; - $this->_groupsort = "@group desc"; - $this->_groupdistinct= ""; - } - - ////////////////////////////////////////////////////////////////////////////// - - /// connect to searchd server, run given search query through given indexes, - /// and return the search results - function Query ( $query, $index="*", $comment="" ) - { - assert ( empty($this->_reqs) ); - - $this->AddQuery ( $query, $index, $comment ); - $results = $this->RunQueries (); - $this->_reqs = array (); // just in case it failed too early - - if ( !is_array($results) ) - return false; // probably network error; error message should be already filled - - $this->_error = $results[0]["error"]; - $this->_warning = $results[0]["warning"]; - if ( $results[0]["status"]==SEARCHD_ERROR ) - return false; - else - return $results[0]; - } - - /// helper to pack floats in network byte order - function _PackFloat ( $f ) - { - $t1 = pack ( "f", $f ); // machine order - list(,$t2) = unpack ( "L*", $t1 ); // int in machine order - return pack ( "N", $t2 ); - } - - /// add query to multi-query batch - /// returns index into results array from RunQueries() call - function AddQuery ( $query, $index="*", $comment="" ) - { - // mbstring workaround - $this->_MBPush (); - - // build request - $req = pack ( "NNNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker, $this->_sort ); // mode and limits - $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby; - $req .= pack ( "N", strlen($query) ) . $query; // query itself - $req .= pack ( "N", count($this->_weights) ); // weights - foreach ( $this->_weights as $weight ) - $req .= pack ( "N", (int)$weight ); - $req .= pack ( "N", strlen($index) ) . $index; // indexes - $req .= pack ( "N", 1 ); // id64 range marker - $req .= sphPack64 ( $this->_min_id ) . sphPack64 ( $this->_max_id ); // id64 range - - // filters - $req .= pack ( "N", count($this->_filters) ); - foreach ( $this->_filters as $filter ) - { - $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"]; - $req .= pack ( "N", $filter["type"] ); - switch ( $filter["type"] ) - { - case SPH_FILTER_VALUES: - $req .= pack ( "N", count($filter["values"]) ); - foreach ( $filter["values"] as $value ) - $req .= pack ( "N", floatval($value) ); // this uberhack is to workaround 32bit signed int limit on x32 platforms - break; - - case SPH_FILTER_RANGE: - $req .= pack ( "NN", $filter["min"], $filter["max"] ); - break; - - case SPH_FILTER_FLOATRANGE: - $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] ); - break; - - default: - assert ( 0 && "internal error: unhandled filter type" ); - } - $req .= pack ( "N", $filter["exclude"] ); - } - - // group-by clause, max-matches count, group-sort clause, cutoff count - $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby; - $req .= pack ( "N", $this->_maxmatches ); - $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort; - $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay ); - $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct; - - // anchor point - if ( empty($this->_anchor) ) - { - $req .= pack ( "N", 0 ); - } else - { - $a =& $this->_anchor; - $req .= pack ( "N", 1 ); - $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"]; - $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"]; - $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] ); - } - - // per-index weights - $req .= pack ( "N", count($this->_indexweights) ); - foreach ( $this->_indexweights as $idx=>$weight ) - $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight ); - - // max query time - $req .= pack ( "N", $this->_maxquerytime ); - - // per-field weights - $req .= pack ( "N", count($this->_fieldweights) ); - foreach ( $this->_fieldweights as $field=>$weight ) - $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight ); - - // comment - $req .= pack ( "N", strlen($comment) ) . $comment; - - // mbstring workaround - $this->_MBPop (); - - // store request to requests array - $this->_reqs[] = $req; - return count($this->_reqs)-1; - } - - /// connect to searchd, run queries batch, and return an array of result sets - function RunQueries () - { - if ( empty($this->_reqs) ) - { - $this->_error = "no queries defined, issue AddQuery() first"; - return false; - } - - // mbstring workaround - $this->_MBPush (); - - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop (); - return false; - } - - //////////////////////////// - // send query, get response - //////////////////////////// - - $nreqs = count($this->_reqs); - $req = join ( "", $this->_reqs ); - $len = 4+strlen($req); - $req = pack ( "nnNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, $nreqs ) . $req; // add header - - fwrite ( $fp, $req, $len+8 ); - if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) )) - { - $this->_MBPop (); - return false; - } - - $this->_reqs = array (); - - ////////////////// - // parse response - ////////////////// - - $p = 0; // current position - $max = strlen($response); // max position for checks, to protect against broken responses - - $results = array (); - for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ ) - { - $results[] = array(); - $result =& $results[$ires]; - - $result["error"] = ""; - $result["warning"] = ""; - - // extract status - list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $result["status"] = $status; - if ( $status!=SEARCHD_OK ) - { - list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $message = substr ( $response, $p, $len ); $p += $len; - - if ( $status==SEARCHD_WARNING ) - { - $result["warning"] = $message; - } else - { - $result["error"] = $message; - continue; - } - } - - // read schema - $fields = array (); - $attrs = array (); - - list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - while ( $nfields-->0 && $p<$max ) - { - list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $fields[] = substr ( $response, $p, $len ); $p += $len; - } - $result["fields"] = $fields; - - list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - while ( $nattrs-->0 && $p<$max ) - { - list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $attr = substr ( $response, $p, $len ); $p += $len; - list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $attrs[$attr] = $type; - } - $result["attrs"] = $attrs; - - // read match count - list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - - // read matches - $idx = -1; - while ( $count-->0 && $p<$max ) - { - // index into result array - $idx++; - - // parse document id and weight - if ( $id64 ) - { - $doc = sphUnpack64 ( substr ( $response, $p, 8 ) ); $p += 8; - list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - } else - { - list ( $doc, $weight ) = array_values ( unpack ( "N*N*", - substr ( $response, $p, 8 ) ) ); - $p += 8; - - if ( PHP_INT_SIZE>=8 ) - { - // x64 route, workaround broken unpack() in 5.2.2+ - if ( $doc<0 ) $doc += (1<<32); - } else - { - // x32 route, workaround php signed/unsigned braindamage - $doc = sprintf ( "%u", $doc ); - } - } - $weight = sprintf ( "%u", $weight ); - - // create match entry - if ( $this->_arrayresult ) - $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight ); - else - $result["matches"][$doc]["weight"] = $weight; - - // parse and create attributes - $attrvals = array (); - foreach ( $attrs as $attr=>$type ) - { - // handle floats - if ( $type==SPH_ATTR_FLOAT ) - { - list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); - $attrvals[$attr] = $fval; - continue; - } - - // handle everything else as unsigned ints - list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - if ( $type & SPH_ATTR_MULTI ) - { - $attrvals[$attr] = array (); - $nvalues = $val; - while ( $nvalues-->0 && $p<$max ) - { - list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $attrvals[$attr][] = sprintf ( "%u", $val ); - } - } else - { - $attrvals[$attr] = sprintf ( "%u", $val ); - } - } - - if ( $this->_arrayresult ) - $result["matches"][$idx]["attrs"] = $attrvals; - else - $result["matches"][$doc]["attrs"] = $attrvals; - } - - list ( $total, $total_found, $msecs, $words ) = - array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) ); - $result["total"] = sprintf ( "%u", $total ); - $result["total_found"] = sprintf ( "%u", $total_found ); - $result["time"] = sprintf ( "%.3f", $msecs/1000 ); - $p += 16; - - while ( $words-->0 && $p<$max ) - { - list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $word = substr ( $response, $p, $len ); $p += $len; - list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; - $result["words"][$word] = array ( - "docs"=>sprintf ( "%u", $docs ), - "hits"=>sprintf ( "%u", $hits ) ); - } - } - - $this->_MBPop (); - return $results; - } - - ///////////////////////////////////////////////////////////////////////////// - // excerpts generation - ///////////////////////////////////////////////////////////////////////////// - - /// connect to searchd server, and generate exceprts (snippets) - /// of given documents for given query. returns false on failure, - /// an array of snippets on success - function BuildExcerpts ( $docs, $index, $words, $opts=array() ) - { - assert ( is_array($docs) ); - assert ( is_string($index) ); - assert ( is_string($words) ); - assert ( is_array($opts) ); - - $this->_MBPush (); - - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop(); - return false; - } - - ///////////////// - // fixup options - ///////////////// - - if ( !isset($opts["before_match"]) ) $opts["before_match"] = ""; - if ( !isset($opts["after_match"]) ) $opts["after_match"] = ""; - if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... "; - if ( !isset($opts["limit"]) ) $opts["limit"] = 256; - if ( !isset($opts["around"]) ) $opts["around"] = 5; - if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false; - if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false; - if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false; - if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false; - - ///////////////// - // build request - ///////////////// - - // v.1.0 req - $flags = 1; // remove spaces - if ( $opts["exact_phrase"] ) $flags |= 2; - if ( $opts["single_passage"] ) $flags |= 4; - if ( $opts["use_boundaries"] ) $flags |= 8; - if ( $opts["weight_order"] ) $flags |= 16; - $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags - $req .= pack ( "N", strlen($index) ) . $index; // req index - $req .= pack ( "N", strlen($words) ) . $words; // req words - - // options - $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"]; - $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"]; - $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"]; - $req .= pack ( "N", (int)$opts["limit"] ); - $req .= pack ( "N", (int)$opts["around"] ); - - // documents - $req .= pack ( "N", count($docs) ); - foreach ( $docs as $doc ) - { - assert ( is_string($doc) ); - $req .= pack ( "N", strlen($doc) ) . $doc; - } - - //////////////////////////// - // send query, get response - //////////////////////////// - - $len = strlen($req); - $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header - $wrote = fwrite ( $fp, $req, $len+8 ); - if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) )) - { - $this->_MBPop (); - return false; - } - - ////////////////// - // parse response - ////////////////// - - $pos = 0; - $res = array (); - $rlen = strlen($response); - for ( $i=0; $i $rlen ) - { - $this->_error = "incomplete reply"; - $this->_MBPop (); - return false; - } - $res[] = $len ? substr ( $response, $pos, $len ) : ""; - $pos += $len; - } - - $this->_MBPop (); - return $res; - } - - - ///////////////////////////////////////////////////////////////////////////// - // keyword generation - ///////////////////////////////////////////////////////////////////////////// - - /// connect to searchd server, and generate keyword list for a given query - /// returns false on failure, - /// an array of words on success - function BuildKeywords ( $query, $index, $hits ) - { - assert ( is_string($query) ); - assert ( is_string($index) ); - assert ( is_bool($hits) ); - - $this->_MBPush (); - - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop(); - return false; - } - - ///////////////// - // build request - ///////////////// - - // v.1.0 req - $req = pack ( "N", strlen($query) ) . $query; // req query - $req .= pack ( "N", strlen($index) ) . $index; // req index - $req .= pack ( "N", (int)$hits ); - - //////////////////////////// - // send query, get response - //////////////////////////// - - $len = strlen($req); - $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header - $wrote = fwrite ( $fp, $req, $len+8 ); - if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) )) - { - $this->_MBPop (); - return false; - } - - ////////////////// - // parse response - ////////////////// - - $pos = 0; - $res = array (); - $rlen = strlen($response); - list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) ); - $pos += 4; - for ( $i=0; $i<$nwords; $i++ ) - { - list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; - $tokenized = $len ? substr ( $response, $pos, $len ) : ""; - $pos += $len; - - list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; - $normalized = $len ? substr ( $response, $pos, $len ) : ""; - $pos += $len; - - $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized ); - - if ( $hits ) - { - list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) ); - $pos += 8; - $res [$i]["docs"] = $ndocs; - $res [$i]["hits"] = $nhits; - } - - if ( $pos > $rlen ) - { - $this->_error = "incomplete reply"; - $this->_MBPop (); - return false; - } - } - - $this->_MBPop (); - return $res; - } - - function EscapeString ( $string ) - { - $from = array ( '(',')','|','-','!','@','~','"','&', '/' ); - $to = array ( '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/' ); - - return str_replace ( $from, $to, $string ); - } - - ///////////////////////////////////////////////////////////////////////////// - // attribute updates - ///////////////////////////////////////////////////////////////////////////// - - /// update given attribute values on given documents in given indexes - /// returns amount of updated documents (0 or more) on success, or -1 on failure - function UpdateAttributes ( $index, $attrs, $values ) - { - // verify everything - assert ( is_string($index) ); - - assert ( is_array($attrs) ); - foreach ( $attrs as $attr ) - assert ( is_string($attr) ); - - assert ( is_array($values) ); - foreach ( $values as $id=>$entry ) - { - assert ( is_numeric($id) ); - assert ( is_array($entry) ); - assert ( count($entry)==count($attrs) ); - foreach ( $entry as $v ) - assert ( is_int($v) ); - } - - // build request - $req = pack ( "N", strlen($index) ) . $index; - - $req .= pack ( "N", count($attrs) ); - foreach ( $attrs as $attr ) - $req .= pack ( "N", strlen($attr) ) . $attr; - - $req .= pack ( "N", count($values) ); - foreach ( $values as $id=>$entry ) - { - $req .= sphPack64 ( $id ); - foreach ( $entry as $v ) - $req .= pack ( "N", $v ); - } - - // mbstring workaround - $this->_MBPush (); - - // connect, send query, get response - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop (); - return -1; - } - - $len = strlen($req); - $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header - fwrite ( $fp, $req, $len+8 ); - - if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) )) - { - $this->_MBPop (); - return -1; - } - - // parse response - list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) ); - $this->_MBPop (); - return $updated; - } -} - -// -// $Id$ -// From 02588069f045ae48984d68c9948c8ecd1c78580d Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 26 Jun 2012 19:06:19 +0530 Subject: [PATCH 0234/1142] [feature/sphinx-fulltext-search] fix config variables config variables now use class property for unique id PHPBB3-10946 Conflicts: phpBB/includes/search/fulltext_sphinx.php --- phpBB/includes/search/fulltext_sphinx.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 9ae6438af2..fb16c5639b 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -226,7 +226,7 @@ class phpbb_search_fulltext_sphinx $config_object = new sphinx_config($config['fulltext_sphinx_config_path'] . 'sphinx.conf'); $config_data = array( - "source source_phpbb_{$this->id}_main" => array( + 'source source_phpbb_' . $this->id . '_main' => array( array('type', 'mysql'), array('sql_host', $dbhost), array('sql_user', $dbuser), @@ -265,7 +265,7 @@ class phpbb_search_fulltext_sphinx array('sql_attr_timestamp' , 'topic_last_post_time'), array('sql_attr_str2ordinal', 'post_subject'), ), - "source source_phpbb_{$this->id}_delta : source_phpbb_{$this->id}_main" => array( + 'source source_phpbb_' . $this->id . '_delta : source_phpbb_' . $this->id . '_main' => array( array('sql_query_pre', ''), array('sql_query_range', ''), array('sql_range_step', ''), @@ -286,9 +286,9 @@ class phpbb_search_fulltext_sphinx p.topic_id = t.topic_id AND p.post_id >= ( SELECT max_doc_id FROM ' . SPHINX_TABLE . ' WHERE counter_id=1 )'), ), - "index index_phpbb_{$this->id}_main" => array( - array('path', $config['fulltext_sphinx_data_path'] . "index_phpbb_{$this->id}_main"), - array('source', "source_phpbb_{$this->id}_main"), + 'index index_phpbb_' . $this->id . '_main' => array( + array('path', $config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main'), + array('source', 'source_phpbb_' . $this->id . '_main'), array('docinfo', 'extern'), array('morphology', 'none'), array('stopwords', (file_exists($config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt') && $config['fulltext_sphinx_stopwords']) ? $config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt' : ''), @@ -298,9 +298,9 @@ class phpbb_search_fulltext_sphinx array('min_prefix_len', '0'), array('min_infix_len', '0'), ), - "index index_phpbb_{$this->id}_delta : index_phpbb_{$this->id}_main" => array( - array('path', $config['fulltext_sphinx_data_path'] . "index_phpbb_{$this->id}_delta"), - array('source', "source_phpbb_{$this->id}_delta"), + 'index index_phpbb_' . $this->id . '_delta : index_phpbb_' . $this->id . '_main' => array( + array('path', $config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta'), + array('source', 'source_phpbb_' . $this->id . '_delta'), ), 'indexer' => array( array('mem_limit', $config['fulltext_sphinx_indexer_mem_limit'] . 'M'), From 74a7407927cd5b54328a3941a9926ee35caf17b4 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Wed, 27 Jun 2012 00:17:39 +0530 Subject: [PATCH 0235/1142] [feature/sphinx-fulltext-search] improve classes in functions-sphinx.php PHPBB3-10946 --- phpBB/includes/functions_sphinx.php | 38 +++++++++++++---------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/phpBB/includes/functions_sphinx.php b/phpBB/includes/functions_sphinx.php index 0f83f8cfb5..a4f0e41491 100644 --- a/phpBB/includes/functions_sphinx.php +++ b/phpBB/includes/functions_sphinx.php @@ -46,16 +46,14 @@ class sphinx_config */ function &get_section_by_name($name) { - for ($i = 0, $n = sizeof($this->sections); $i < $n; $i++) + for ($i = 0, $size = sizeof($this->sections); $i < $size; $i++) { // make sure this is really a section object and not a comment - if (is_a($this->sections[$i], 'sphinx_config_section') && $this->sections[$i]->get_name() == $name) + if (($this->sections[$i] instanceof sphinx_config_section) && $this->sections[$i]->get_name() == $name) { return $this->sections[$i]; } } - $null = null; - return $null; } /** @@ -116,7 +114,7 @@ class sphinx_config $section_name = ''; $section_name_comment = ''; $found_opening_bracket = false; - for ($j = 0, $n = strlen($line); $j < $n; $j++) + for ($j = 0, $length = strlen($line); $j < $length; $j++) { if ($line[$j] == '#') { @@ -189,15 +187,15 @@ class sphinx_config $in_value = false; $end_section = false; - // ... then we should prase this line char by char: - // - first there's the variable name - // - then an equal sign - // - the variable value - // - possibly a backslash before the linefeed in this case we need to continue - // parsing the value in the next line - // - a # indicating that the rest of the line is a comment - // - a closing curly bracket indicating the end of this section - for ($j = 0, $n = strlen($line); $j < $n; $j++) + /* ... then we should prase this line char by char: + - first there's the variable name + - then an equal sign + - the variable value + - possibly a backslash before the linefeed in this case we need to continue + parsing the value in the next line + - a # indicating that the rest of the line is a comment + - a closing curly bracket indicating the end of this section*/ + for ($j = 0, $length = strlen($line); $j < $length; $j++) { if ($line[$j] == '#') { @@ -223,7 +221,7 @@ class sphinx_config } else { - if ($line[$j] == '\\' && $j == $n - 1) + if ($line[$j] == '\\' && $j == $length - 1) { $value .= "\n"; $in_value = true; @@ -349,16 +347,14 @@ class sphinx_config_section */ function &get_variable_by_name($name) { - for ($i = 0, $n = sizeof($this->variables); $i < $n; $i++) + for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) { // make sure this is a variable object and not a comment - if (is_a($this->variables[$i], 'sphinx_config_variable') && $this->variables[$i]->get_name() == $name) + if (($this->variables[$i] instanceof sphinx_config_variable) && $this->variables[$i]->get_name() == $name) { return $this->variables[$i]; } } - $null = null; - return $null; } /** @@ -368,10 +364,10 @@ class sphinx_config_section */ function delete_variables_by_name($name) { - for ($i = 0; $i < sizeof($this->variables); $i++) + for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) { // make sure this is a variable object and not a comment - if (is_a($this->variables[$i], 'sphinx_config_variable') && $this->variables[$i]->get_name() == $name) + if (($this->variables[$i] instanceof sphinx_config_variable) && $this->variables[$i]->get_name() == $name) { array_splice($this->variables, $i, 1); $i--; From f609555b1ae335b5ea996bf26ee2846058e5256a Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Wed, 27 Jun 2012 00:28:31 +0530 Subject: [PATCH 0236/1142] [feature/sphinx-fulltext-search] integrate sphinx language keys with core Language keys removed from mods folder and added to language/en/acp/search.php PHPBB3-10946 --- phpBB/includes/search/fulltext_sphinx.php | 6 -- phpBB/language/en/acp/search.php | 30 ++++++++++ phpBB/language/en/mods/fulltext_sphinx.php | 65 ---------------------- 3 files changed, 30 insertions(+), 71 deletions(-) delete mode 100644 phpBB/language/en/mods/fulltext_sphinx.php diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index fb16c5639b..2e263c1b55 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -733,8 +733,6 @@ class phpbb_search_fulltext_sphinx if (!isset($config['fulltext_sphinx_configured']) || !$config['fulltext_sphinx_configured']) { - $user->add_lang('mods/fulltext_sphinx'); - return $user->lang['FULLTEXT_SPHINX_CONFIGURE_FIRST']; } @@ -902,8 +900,6 @@ class phpbb_search_fulltext_sphinx $this->get_stats(); } - $user->add_lang('mods/fulltext_sphinx'); - return array( $user->lang['FULLTEXT_SPHINX_MAIN_POSTS'] => ($this->index_created()) ? $this->stats['main_posts'] : 0, $user->lang['FULLTEXT_SPHINX_DELTA_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0, @@ -966,8 +962,6 @@ class phpbb_search_fulltext_sphinx { global $user, $config; - $user->add_lang('mods/fulltext_sphinx'); - $config_vars = array( 'fulltext_sphinx_autoconf' => 'bool', 'fulltext_sphinx_autorun' => 'bool', diff --git a/phpBB/language/en/acp/search.php b/phpBB/language/en/acp/search.php index cd319c66a9..3fa7f34c64 100644 --- a/phpBB/language/en/acp/search.php +++ b/phpBB/language/en/acp/search.php @@ -69,6 +69,36 @@ $lang = array_merge($lang, array( 'FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN' => 'Words with at least this many characters will be included in the query to the database.', 'FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN' => 'Words with no more than this many characters will be included in the query to the database.', + 'FULLTEXT_SPHINX_AUTOCONF' => 'Automatically configure Sphinx', + 'FULLTEXT_SPHINX_AUTOCONF_EXPLAIN' => 'This is the easiest way to install Sphinx, just select the settings here and a config file will be written for you. This requires write permissions on the configuration folder.', + 'FULLTEXT_SPHINX_AUTORUN' => 'Automatically run Sphinx', + 'FULLTEXT_SPHINX_AUTORUN_EXPLAIN' => 'This is the easiest way to run Sphinx. Select the paths in this dialogue and the Sphinx daemon will be started and stopped as needed. You can also create an index from the ACP. If your PHP installation forbids the use of exec you can disable this and run Sphinx manually.', + 'FULLTEXT_SPHINX_BIN_PATH' => 'Path to executables directory', + 'FULLTEXT_SPHINX_BIN_PATH_EXPLAIN' => 'Skip if autorun is disabled. If this path could not be determined automatically you have to enter the path to the directory in which the sphinx executables indexer and searchd reside.', + 'FULLTEXT_SPHINX_CONFIG_PATH' => 'Path to configuration directory', + 'FULLTEXT_SPHINX_CONFIG_PATH_EXPLAIN' => 'Skip if autoconf is disabled. You should create this config directory outside the web accessable directories. It has to be writable by the user as which your webserver is running (often www-data or nobody).', + 'FULLTEXT_SPHINX_CONFIGURE_FIRST' => 'Before you create an index you have to enable and configure sphinx under GENERAL -> SERVER CONFIGURATION -> Search settings.', + 'FULLTEXT_SPHINX_CONFIGURE_BEFORE' => 'Configure the following settings BEFORE activating Sphinx', + 'FULLTEXT_SPHINX_CONFIGURE_AFTER' => 'The following settings do not have to be configured before activating Sphinx', + 'FULLTEXT_SPHINX_DATA_PATH' => 'Path to data directory', + 'FULLTEXT_SPHINX_DATA_PATH_EXPLAIN' => 'Skip if autorun is disabled. You should create this directory outside the web accessable directories. It has to be writable by the user as which your webserver is running (often www-data or nobody). It will be used to store the indexes and log files.', + 'FULLTEXT_SPHINX_DELTA_POSTS' => 'Number of posts in frequently updated delta index', + 'FULLTEXT_SPHINX_DIRECTORY_NOT_FOUND' => 'The directory %s does not exist. Please correct your path settings.', + 'FULLTEXT_SPHINX_FILE_NOT_EXECUTABLE' => 'The file %s is not executable for the webserver.', + 'FULLTEXT_SPHINX_FILE_NOT_FOUND' => 'The file %s does not exist. Please correct your path settings.', + 'FULLTEXT_SPHINX_FILE_NOT_WRITABLE' => 'The file %s cannot be written by the webserver.', + 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT' => 'Indexer memory limit', + 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN' => 'This number should at all times be lower than the RAM available on your machine. If you experience periodic performance problems this might be due to the indexer consuming too many resources. It might help to lower the amount of memory available to the indexer.', + 'FULLTEXT_SPHINX_LAST_SEARCHES' => 'Recent search queries', + 'FULLTEXT_SPHINX_MAIN_POSTS' => 'Number of posts in main index', + 'FULLTEXT_SPHINX_PORT' => 'Sphinx search deamon port', + 'FULLTEXT_SPHINX_PORT_EXPLAIN' => 'Port on which the sphinx search deamon on localhost listens. Leave empty to use the default 3312', + 'FULLTEXT_SPHINX_REQUIRES_EXEC' => 'The sphinx plugin for phpBB requires PHP’s exec function which is disabled on your system.', + 'FULLTEXT_SPHINX_UNCONFIGURED' => 'Please set all necessary options in the "Fulltext Sphinx" section of the previous page before you try to activate the sphinx plugin.', + 'FULLTEXT_SPHINX_WRONG_DATABASE' => 'The sphinx plugin for phpBB currently only supports MySQL', + 'FULLTEXT_SPHINX_STOPWORDS_FILE' => 'Stopwords activated', + 'FULLTEXT_SPHINX_STOPWORDS_FILE_EXPLAIN' => 'This setting only works with autoconf enabled. You can place a file called sphinx_stopwords.txt containing one word in each line in your config directory. If this file is present these words will be excluded from the indexing process.', + 'GENERAL_SEARCH_SETTINGS' => 'General search settings', 'GO_TO_SEARCH_INDEX' => 'Go to search index page', diff --git a/phpBB/language/en/mods/fulltext_sphinx.php b/phpBB/language/en/mods/fulltext_sphinx.php deleted file mode 100644 index f3fd68aa62..0000000000 --- a/phpBB/language/en/mods/fulltext_sphinx.php +++ /dev/null @@ -1,65 +0,0 @@ - 'Automatically configure Sphinx', - 'FULLTEXT_SPHINX_AUTOCONF_EXPLAIN' => 'This is the easiest way to install Sphinx, just select the settings here and a config file will be written for you. This requires write permissions on the configuration folder.', - 'FULLTEXT_SPHINX_AUTORUN' => 'Automatically run Sphinx', - 'FULLTEXT_SPHINX_AUTORUN_EXPLAIN' => 'This is the easiest way to run Sphinx. Select the paths in this dialogue and the Sphinx daemon will be started and stopped as needed. You can also create an index from the ACP. If your PHP installation forbids the use of exec you can disable this and run Sphinx manually.', - 'FULLTEXT_SPHINX_BIN_PATH' => 'Path to executables directory', - 'FULLTEXT_SPHINX_BIN_PATH_EXPLAIN' => 'Skip if autorun is disabled. If this path could not be determined automatically you have to enter the path to the directory in which the sphinx executables indexer and searchd reside.', - 'FULLTEXT_SPHINX_CONFIG_PATH' => 'Path to configuration directory', - 'FULLTEXT_SPHINX_CONFIG_PATH_EXPLAIN' => 'Skip if autoconf is disabled. You should create this config directory outside the web accessable directories. It has to be writable by the user as which your webserver is running (often www-data or nobody).', - 'FULLTEXT_SPHINX_CONFIGURE_FIRST' => 'Before you create an index you have to enable and configure sphinx under GENERAL -> SERVER CONFIGURATION -> Search settings.', - 'FULLTEXT_SPHINX_CONFIGURE_BEFORE' => 'Configure the following settings BEFORE activating Sphinx', - 'FULLTEXT_SPHINX_CONFIGURE_AFTER' => 'The following settings do not have to be configured before activating Sphinx', - 'FULLTEXT_SPHINX_DATA_PATH' => 'Path to data directory', - 'FULLTEXT_SPHINX_DATA_PATH_EXPLAIN' => 'Skip if autorun is disabled. You should create this directory outside the web accessable directories. It has to be writable by the user as which your webserver is running (often www-data or nobody). It will be used to store the indexes and log files.', - 'FULLTEXT_SPHINX_DELTA_POSTS' => 'Number of posts in frequently updated delta index', - 'FULLTEXT_SPHINX_DIRECTORY_NOT_FOUND' => 'The directory %s does not exist. Please correct your path settings.', - 'FULLTEXT_SPHINX_FILE_NOT_EXECUTABLE' => 'The file %s is not executable for the webserver.', - 'FULLTEXT_SPHINX_FILE_NOT_FOUND' => 'The file %s does not exist. Please correct your path settings.', - 'FULLTEXT_SPHINX_FILE_NOT_WRITABLE' => 'The file %s cannot be written by the webserver.', - 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT' => 'Indexer memory limit', - 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN' => 'This number should at all times be lower than the RAM available on your machine. If you experience periodic performance problems this might be due to the indexer consuming too many resources. It might help to lower the amount of memory available to the indexer.', - 'FULLTEXT_SPHINX_LAST_SEARCHES' => 'Recent search queries', - 'FULLTEXT_SPHINX_MAIN_POSTS' => 'Number of posts in main index', - 'FULLTEXT_SPHINX_PORT' => 'Sphinx search deamon port', - 'FULLTEXT_SPHINX_PORT_EXPLAIN' => 'Port on which the sphinx search deamon on localhost listens. Leave empty to use the default 3312', - 'FULLTEXT_SPHINX_REQUIRES_EXEC' => 'The sphinx plugin for phpBB requires PHP’s exec function which is disabled on your system.', - 'FULLTEXT_SPHINX_UNCONFIGURED' => 'Please set all necessary options in the "Fulltext Sphinx" section of the previous page before you try to activate the sphinx plugin.', - 'FULLTEXT_SPHINX_WRONG_DATABASE' => 'The sphinx plugin for phpBB currently only supports MySQL', - 'FULLTEXT_SPHINX_STOPWORDS_FILE' => 'Stopwords activated', - 'FULLTEXT_SPHINX_STOPWORDS_FILE_EXPLAIN' => 'This setting only works with autoconf enabled. You can place a file called sphinx_stopwords.txt containing one word in each line in your config directory. If this file is present these words will be excluded from the indexing process.', -)); - -?> From bfd01f01877bcb9a9be9e2df5c6713c3e338579e Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Wed, 27 Jun 2012 01:03:04 +0530 Subject: [PATCH 0237/1142] [feature/sphinx-fulltext-search] remove all reference returns PHPBB3-10946 --- phpBB/includes/functions_sphinx.php | 8 ++++---- phpBB/includes/search/fulltext_sphinx.php | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/phpBB/includes/functions_sphinx.php b/phpBB/includes/functions_sphinx.php index a4f0e41491..a93a1d950f 100644 --- a/phpBB/includes/functions_sphinx.php +++ b/phpBB/includes/functions_sphinx.php @@ -44,7 +44,7 @@ class sphinx_config * @param string $name The name of the section that shall be returned * @return sphinx_config_section The section object or null if none was found */ - function &get_section_by_name($name) + function get_section_by_name($name) { for ($i = 0, $size = sizeof($this->sections); $i < $size; $i++) { @@ -62,7 +62,7 @@ class sphinx_config * @param string $name The name for the new section * @return sphinx_config_section The newly created section object */ - function &add_section($name) + function add_section($name) { $this->sections[] = new sphinx_config_section($name, ''); return $this->sections[sizeof($this->sections) - 1]; @@ -345,7 +345,7 @@ class sphinx_config_section * @return sphinx_config_section The first variable object from this section with the * given name or null if none was found */ - function &get_variable_by_name($name) + function get_variable_by_name($name) { for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) { @@ -382,7 +382,7 @@ class sphinx_config_section * @param string $value The value for the new variable * @return sphinx_config_variable Variable object that was created */ - function &create_variable($name, $value) + function create_variable($name, $value) { $this->variables[] = new sphinx_config_variable($name, $value, ''); return $this->variables[sizeof($this->variables) - 1]; diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 2e263c1b55..477b1646fb 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -323,10 +323,10 @@ class phpbb_search_fulltext_sphinx foreach ($config_data as $section_name => $section_data) { - $section = &$config_object->get_section_by_name($section_name); + $section = $config_object->get_section_by_name($section_name); if (!$section) { - $section = &$config_object->add_section($section_name); + $section = $config_object->add_section($section_name); } foreach ($delete as $key => $void) @@ -346,10 +346,10 @@ class phpbb_search_fulltext_sphinx if (!isset($non_unique[$key])) { - $variable = &$section->get_variable_by_name($key); + $variable = $section->get_variable_by_name($key); if (!$variable) { - $variable = &$section->create_variable($key, $value); + $variable = $section->create_variable($key, $value); } else { @@ -358,7 +358,7 @@ class phpbb_search_fulltext_sphinx } else { - $variable = &$section->create_variable($key, $value); + $variable = $section->create_variable($key, $value); } } } From 39f8a5fa9f71724d0abd98cdf7a7d82fc7e7bb0f Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Wed, 27 Jun 2012 03:44:03 +0530 Subject: [PATCH 0238/1142] [feature/sphinx-fulltext-search] use sql_build_query for query Uses sql_build_query for JOIN query. Remove casting to int and space for phpbb conventions to be followed PHPBB3-10946 Conflicts: phpBB/includes/search/fulltext_sphinx.php --- phpBB/includes/search/fulltext_sphinx.php | 24 ++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 477b1646fb..2690612b1a 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -53,7 +53,7 @@ class phpbb_search_fulltext_sphinx $this->id = $config['avatar_salt']; $this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main'; - $this->sphinx = new SphinxClient (); + $this->sphinx = new SphinxClient(); if (!empty($config['fulltext_sphinx_configured'])) { @@ -648,18 +648,28 @@ class phpbb_search_fulltext_sphinx } else if ($mode != 'post' && $post_id) { - // update topic_last_post_time for full topic - $sql = 'SELECT p1.post_id - FROM ' . POSTS_TABLE . ' p1 - LEFT JOIN ' . POSTS_TABLE . ' p2 ON (p1.topic_id = p2.topic_id) - WHERE p2.post_id = ' . $post_id; + // Update topic_last_post_time for full topic + $sql_array = array( + 'SELECT' => 'p1.post_id', + 'FROM' => array( + POSTS_TABLE => 'p1', + ), + 'LEFT_JOIN' => array(array( + 'FROM' => array( + POSTS_TABLE => 'p2' + ), + 'ON' => 'p1.topic_id = p2.topic_id', + )), + ); + + $sql = $db->sql_build_query('SELECT', $sql_array); $result = $db->sql_query($sql); $post_updates = array(); $post_time = time(); while ($row = $db->sql_fetchrow($result)) { - $post_updates[(int)$row['post_id']] = array((int) $post_time); + $post_updates[(int)$row['post_id']] = array($post_time); } $db->sql_freeresult($result); From 10b706674e0fc100ff4e21d5fe100a9b532bb4bf Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 10 Jul 2012 01:49:38 +0530 Subject: [PATCH 0239/1142] [feature/sphinx-fulltext-search] add binlog_path to config binlog files are now added to the data folder. PHPBB3-10946 --- phpBB/docs/sphinx.sample.conf | 1 + phpBB/includes/search/fulltext_sphinx.php | 1 + 2 files changed, 2 insertions(+) diff --git a/phpBB/docs/sphinx.sample.conf b/phpBB/docs/sphinx.sample.conf index d7e59a11fc..000d8157d6 100644 --- a/phpBB/docs/sphinx.sample.conf +++ b/phpBB/docs/sphinx.sample.conf @@ -93,4 +93,5 @@ searchd max_children = 30 pid_file = {DATA_PATH}/searchd.pid max_matches = 20000 + binlog_path = {DATA_PATH} } diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 2690612b1a..8514b9cabb 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -315,6 +315,7 @@ class phpbb_search_fulltext_sphinx array('max_children', '30'), array('pid_file', $config['fulltext_sphinx_data_path'] . "searchd.pid"), array('max_matches', (string) MAX_MATCHES), + array('binlog_path', $config['fulltext_sphinx_data_path']), ), ); From 97fda78e7d85444f21ba2b10b3d58c4639b85936 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Sun, 1 Jul 2012 01:23:57 +0530 Subject: [PATCH 0240/1142] [feature/sphinx-fulltext-search] Make different files for different classes Break the classes in functions-sphinx.php into different files with proper class names according to phpbb class auto loader conventions. PHPBB3-10946 Conflicts: phpBB/includes/search/sphinx/config.php --- phpBB/includes/search/fulltext_sphinx.php | 7 +- .../sphinx/config.php} | 251 ++---------------- .../includes/search/sphinx/config_comment.php | 45 ++++ .../includes/search/sphinx/config_section.php | 144 ++++++++++ .../search/sphinx/config_variable.php | 72 +++++ 5 files changed, 280 insertions(+), 239 deletions(-) rename phpBB/includes/{functions_sphinx.php => search/sphinx/config.php} (51%) create mode 100644 phpBB/includes/search/sphinx/config_comment.php create mode 100644 phpBB/includes/search/sphinx/config_section.php create mode 100644 phpBB/includes/search/sphinx/config_variable.php diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 8514b9cabb..6f3c688aed 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -207,11 +207,6 @@ class phpbb_search_fulltext_sphinx // now that we're sure everything was entered correctly, generate a config for the index // we misuse the avatar_salt for this, as it should be unique ;-) - if (!class_exists('sphinx_config')) - { - include($phpbb_root_path . 'includes/functions_sphinx.php'); - } - if (!file_exists($config['fulltext_sphinx_config_path'] . 'sphinx.conf')) { $filename = $config['fulltext_sphinx_config_path'] . 'sphinx.conf'; @@ -223,7 +218,7 @@ class phpbb_search_fulltext_sphinx @fclose($fp); } - $config_object = new sphinx_config($config['fulltext_sphinx_config_path'] . 'sphinx.conf'); + $config_object = new phpbb_search_sphinx_config($config['fulltext_sphinx_config_path'] . 'sphinx.conf'); $config_data = array( 'source source_phpbb_' . $this->id . '_main' => array( diff --git a/phpBB/includes/functions_sphinx.php b/phpBB/includes/search/sphinx/config.php similarity index 51% rename from phpBB/includes/functions_sphinx.php rename to phpBB/includes/search/sphinx/config.php index a93a1d950f..966cd0f284 100644 --- a/phpBB/includes/functions_sphinx.php +++ b/phpBB/includes/search/sphinx/config.php @@ -1,13 +1,14 @@ sections); $i < $size; $i++) { // make sure this is really a section object and not a comment - if (($this->sections[$i] instanceof sphinx_config_section) && $this->sections[$i]->get_name() == $name) + if (($this->sections[$i] instanceof phpbb_search_sphinx_config_section) && $this->sections[$i]->get_name() == $name) { return $this->sections[$i]; } @@ -59,12 +60,12 @@ class sphinx_config /** * Appends a new empty section to the end of the config * - * @param string $name The name for the new section - * @return sphinx_config_section The newly created section object + * @param string $name The name for the new section + * @return phpbb_search_sphinx_config_section The newly created section object */ function add_section($name) { - $this->sections[] = new sphinx_config_section($name, ''); + $this->sections[] = new phpbb_search_sphinx_config_section($name, ''); return $this->sections[sizeof($this->sections) - 1]; } @@ -104,7 +105,7 @@ class sphinx_config // that way they're not deleted when reassembling the file from the sections if (!$line || $line[0] == '#') { - $this->sections[] = new sphinx_config_comment($config_file[$i]); + $this->sections[] = new phpbb_search_sphinx_config_comment($config_file[$i]); continue; } else @@ -138,7 +139,7 @@ class sphinx_config // and then we create the new section object $section_name = trim($section_name); - $section = new sphinx_config_section($section_name, $section_name_comment); + $section = new phpbb_search_sphinx_config_section($section_name, $section_name_comment); } } else // if we're looking for variables inside a section @@ -152,7 +153,7 @@ class sphinx_config // of this section so they're not deleted on reassembly if (!$line || $line[0] == '#') { - $section->add_variable(new sphinx_config_comment($config_file[$i])); + $section->add_variable(new phpbb_search_sphinx_config_comment($config_file[$i])); continue; } @@ -168,7 +169,7 @@ class sphinx_config } else { - $section->add_variable(new sphinx_config_comment($config_file[$i])); + $section->add_variable(new phpbb_search_sphinx_config_comment($config_file[$i])); continue; } } @@ -234,7 +235,7 @@ class sphinx_config // if a name and an equal sign were found then we have append a new variable object to the section if ($name && $found_assignment) { - $section->add_variable(new sphinx_config_variable(trim($name), trim($value), ($end_section) ? '' : $comment)); + $section->add_variable(new phpbb_search_sphinx_config_variable(trim($name), trim($value), ($end_section) ? '' : $comment)); continue; } @@ -251,7 +252,7 @@ class sphinx_config // if we did not find anything meaningful up to here, then just treat it as a comment $comment = ($skip_first) ? "\t" . substr(ltrim($config_file[$i]), 1) : $config_file[$i]; - $section->add_variable(new sphinx_config_comment($comment)); + $section->add_variable(new phpbb_search_sphinx_config_comment($comment)); } } @@ -284,219 +285,3 @@ class sphinx_config fclose($fp); } } - -/** -* sphinx_config_section -* Represents a single section inside the sphinx configuration -*/ -class sphinx_config_section -{ - var $name; - var $comment; - var $end_comment; - var $variables = array(); - - /** - * Construct a new section - * - * @param string $name Name of the section - * @param string $comment Comment that should be appended after the name in the - * textual format. - */ - function sphinx_config_section($name, $comment) - { - $this->name = $name; - $this->comment = $comment; - $this->end_comment = ''; - } - - /** - * Add a variable object to the list of variables in this section - * - * @param sphinx_config_variable $variable The variable object - */ - function add_variable($variable) - { - $this->variables[] = $variable; - } - - /** - * Adds a comment after the closing bracket in the textual representation - */ - function set_end_comment($end_comment) - { - $this->end_comment = $end_comment; - } - - /** - * Getter for the name of this section - * - * @return string Section's name - */ - function get_name() - { - return $this->name; - } - - /** - * Get a variable object by its name - * - * @param string $name The name of the variable that shall be returned - * @return sphinx_config_section The first variable object from this section with the - * given name or null if none was found - */ - function get_variable_by_name($name) - { - for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) - { - // make sure this is a variable object and not a comment - if (($this->variables[$i] instanceof sphinx_config_variable) && $this->variables[$i]->get_name() == $name) - { - return $this->variables[$i]; - } - } - } - - /** - * Deletes all variables with the given name - * - * @param string $name The name of the variable objects that are supposed to be removed - */ - function delete_variables_by_name($name) - { - for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) - { - // make sure this is a variable object and not a comment - if (($this->variables[$i] instanceof sphinx_config_variable) && $this->variables[$i]->get_name() == $name) - { - array_splice($this->variables, $i, 1); - $i--; - } - } - } - - /** - * Create a new variable object and append it to the variable list of this section - * - * @param string $name The name for the new variable - * @param string $value The value for the new variable - * @return sphinx_config_variable Variable object that was created - */ - function create_variable($name, $value) - { - $this->variables[] = new sphinx_config_variable($name, $value, ''); - return $this->variables[sizeof($this->variables) - 1]; - } - - /** - * Turns this object into a string which can be written to a config file - * - * @return string Config data in textual form, parsable for sphinx - */ - function to_string() - { - $content = $this->name . " " . $this->comment . "\n{\n"; - - // make sure we don't get too many newlines after the opening bracket - while (trim($this->variables[0]->to_string()) == "") - { - array_shift($this->variables); - } - - foreach ($this->variables as $variable) - { - $content .= $variable->to_string(); - } - $content .= '}' . $this->end_comment . "\n"; - - return $content; - } -} - -/** -* sphinx_config_variable -* Represents a single variable inside the sphinx configuration -*/ -class sphinx_config_variable -{ - var $name; - var $value; - var $comment; - - /** - * Constructs a new variable object - * - * @param string $name Name of the variable - * @param string $value Value of the variable - * @param string $comment Optional comment after the variable in the - * config file - */ - function sphinx_config_variable($name, $value, $comment) - { - $this->name = $name; - $this->value = $value; - $this->comment = $comment; - } - - /** - * Getter for the variable's name - * - * @return string The variable object's name - */ - function get_name() - { - return $this->name; - } - - /** - * Allows changing the variable's value - * - * @param string $value New value for this variable - */ - function set_value($value) - { - $this->value = $value; - } - - /** - * Turns this object into a string readable by sphinx - * - * @return string Config data in textual form - */ - function to_string() - { - return "\t" . $this->name . ' = ' . str_replace("\n", "\\\n", $this->value) . ' ' . $this->comment . "\n"; - } -} - - -/** -* sphinx_config_comment -* Represents a comment inside the sphinx configuration -*/ -class sphinx_config_comment -{ - var $exact_string; - - /** - * Create a new comment - * - * @param string $exact_string The content of the comment including newlines, leading whitespace, etc. - */ - function sphinx_config_comment($exact_string) - { - $this->exact_string = $exact_string; - } - - /** - * Simply returns the comment as it was created - * - * @return string The exact string that was specified in the constructor - */ - function to_string() - { - return $this->exact_string; - } -} - -?> diff --git a/phpBB/includes/search/sphinx/config_comment.php b/phpBB/includes/search/sphinx/config_comment.php new file mode 100644 index 0000000000..63d3488aef --- /dev/null +++ b/phpBB/includes/search/sphinx/config_comment.php @@ -0,0 +1,45 @@ +exact_string = $exact_string; + } + + /** + * Simply returns the comment as it was created + * + * @return string The exact string that was specified in the constructor + */ + function to_string() + { + return $this->exact_string; + } +} diff --git a/phpBB/includes/search/sphinx/config_section.php b/phpBB/includes/search/sphinx/config_section.php new file mode 100644 index 0000000000..529254dd5a --- /dev/null +++ b/phpBB/includes/search/sphinx/config_section.php @@ -0,0 +1,144 @@ +name = $name; + $this->comment = $comment; + $this->end_comment = ''; + } + + /** + * Add a variable object to the list of variables in this section + * + * @param phpbb_search_sphinx_config_variable $variable The variable object + */ + function add_variable($variable) + { + $this->variables[] = $variable; + } + + /** + * Adds a comment after the closing bracket in the textual representation + */ + function set_end_comment($end_comment) + { + $this->end_comment = $end_comment; + } + + /** + * Getter for the name of this section + * + * @return string Section's name + */ + function get_name() + { + return $this->name; + } + + /** + * Get a variable object by its name + * + * @param string $name The name of the variable that shall be returned + * @return phpbb_search_sphinx_config_section The first variable object from this section with the + * given name or null if none was found + */ + function get_variable_by_name($name) + { + for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) + { + // make sure this is a variable object and not a comment + if (($this->variables[$i] instanceof phpbb_search_sphinx_config_variable) && $this->variables[$i]->get_name() == $name) + { + return $this->variables[$i]; + } + } + } + + /** + * Deletes all variables with the given name + * + * @param string $name The name of the variable objects that are supposed to be removed + */ + function delete_variables_by_name($name) + { + for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) + { + // make sure this is a variable object and not a comment + if (($this->variables[$i] instanceof phpbb_search_sphinx_config_variable) && $this->variables[$i]->get_name() == $name) + { + array_splice($this->variables, $i, 1); + $i--; + } + } + } + + /** + * Create a new variable object and append it to the variable list of this section + * + * @param string $name The name for the new variable + * @param string $value The value for the new variable + * @return phpbb_search_sphinx_config_variable Variable object that was created + */ + function create_variable($name, $value) + { + $this->variables[] = new phpbb_search_sphinx_config_variable($name, $value, ''); + return $this->variables[sizeof($this->variables) - 1]; + } + + /** + * Turns this object into a string which can be written to a config file + * + * @return string Config data in textual form, parsable for sphinx + */ + function to_string() + { + $content = $this->name . ' ' . $this->comment . "\n{\n"; + + // make sure we don't get too many newlines after the opening bracket + while (trim($this->variables[0]->to_string()) == '') + { + array_shift($this->variables); + } + + foreach ($this->variables as $variable) + { + $content .= $variable->to_string(); + } + $content .= '}' . $this->end_comment . "\n"; + + return $content; + } +} diff --git a/phpBB/includes/search/sphinx/config_variable.php b/phpBB/includes/search/sphinx/config_variable.php new file mode 100644 index 0000000000..dd7836f7c8 --- /dev/null +++ b/phpBB/includes/search/sphinx/config_variable.php @@ -0,0 +1,72 @@ +name = $name; + $this->value = $value; + $this->comment = $comment; + } + + /** + * Getter for the variable's name + * + * @return string The variable object's name + */ + function get_name() + { + return $this->name; + } + + /** + * Allows changing the variable's value + * + * @param string $value New value for this variable + */ + function set_value($value) + { + $this->value = $value; + } + + /** + * Turns this object into a string readable by sphinx + * + * @return string Config data in textual form + */ + function to_string() + { + return "\t" . $this->name . ' = ' . str_replace("\n", "\\\n", $this->value) . ' ' . $this->comment . "\n"; + } +} From 4a11a7b97027743b8239d31a4d51824bd807c5ac Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Sun, 1 Jul 2012 03:03:15 +0530 Subject: [PATCH 0241/1142] [feature/sphinx-fulltext-search] add sphinx_table constant to constants.php PHPBB3-10946 --- phpBB/includes/constants.php | 1 + phpBB/includes/search/fulltext_sphinx.php | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 66d2a003c6..68af41ab20 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -260,6 +260,7 @@ define('SESSIONS_TABLE', $table_prefix . 'sessions'); define('SESSIONS_KEYS_TABLE', $table_prefix . 'sessions_keys'); define('SITELIST_TABLE', $table_prefix . 'sitelist'); define('SMILIES_TABLE', $table_prefix . 'smilies'); +define('SPHINX_TABLE', $table_prefix . 'sphinx'); define('STYLES_TABLE', $table_prefix . 'styles'); define('STYLES_TEMPLATE_TABLE', $table_prefix . 'styles_template'); define('STYLES_TEMPLATE_DATA_TABLE',$table_prefix . 'styles_template_data'); diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 6f3c688aed..53bff898eb 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -26,7 +26,6 @@ require($phpbb_root_path . "includes/sphinxapi-0.9.8." . $phpEx); define('INDEXER_NAME', 'indexer'); define('SEARCHD_NAME', 'searchd'); -define('SPHINX_TABLE', $table_prefix . 'sphinx'); define('MAX_MATCHES', 20000); define('CONNECT_RETRIES', 3); From 06eeed058df75c41496c5306bfa35725c45cf5f3 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Sun, 1 Jul 2012 03:17:45 +0530 Subject: [PATCH 0242/1142] [feature/sphinx-fulltext-search] remove unused arrays PHPBB3-10946 --- phpBB/includes/search/fulltext_sphinx.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 53bff898eb..4c0adcd99e 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -395,14 +395,7 @@ class phpbb_search_fulltext_sphinx $this->sphinx->SetMatchMode(SPH_MATCH_ANY); } - $match = array(); - // Keep quotes - $match[] = "#"#"; - // KeepNew lines - $match[] = "#[\n]+#"; - - $replace = array('"', " "); - + // Keep quotes and new lines $keywords = str_replace(array('"', "\n"), array('"', ' '), trim($keywords)); if (strlen($keywords) > 0) From 0e9174d168a82bde16ec59d615e19b85a50cebcf Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Sun, 1 Jul 2012 05:06:51 +0530 Subject: [PATCH 0243/1142] [feature/sphinx-fulltext-search] use keywords_search instead of get_name using keyword_search method instead of get_name to distinguish between the search backend classes present in includes/search and other helper classes. PHPBB3-10946 --- phpBB/includes/acp/acp_search.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_search.php b/phpBB/includes/acp/acp_search.php index 54a3e7aaa1..82d9b021fe 100644 --- a/phpBB/includes/acp/acp_search.php +++ b/phpBB/includes/acp/acp_search.php @@ -598,7 +598,7 @@ class acp_search { global $phpbb_root_path, $phpEx, $user; - if (!class_exists($type) || !method_exists($type, 'get_name')) + if (!class_exists($type) || !method_exists($type, 'keyword_search')) { $error = $user->lang['NO_SUCH_SEARCH_MODULE']; return $error; From 2503581cd562b39a108821da85cc0175735e24a5 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 10 Jul 2012 02:33:02 +0530 Subject: [PATCH 0244/1142] [feature/sphinx-fulltext-search] add class properties indexes & sphinx PHPBB3-10946 --- phpBB/includes/search/fulltext_sphinx.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 4c0adcd99e..4ace7c9753 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -44,6 +44,8 @@ class phpbb_search_fulltext_sphinx var $search_query; var $common_words = array(); var $id; + var $indexes; + var $sphinx; public function __construct(&$error) { From 8dcdf8a9732a570f26fee802af4bfcbd25f16ec2 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Sun, 1 Jul 2012 12:01:14 +0530 Subject: [PATCH 0245/1142] [feature/sphinx-fulltext-search] add docblock and access modifiers PHPBB3-10946 Conflicts: phpBB/includes/search/fulltext_sphinx.php --- phpBB/includes/search/fulltext_sphinx.php | 85 +++++++++++++++++++---- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 4ace7c9753..82addca28a 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -38,15 +38,21 @@ define('CONNECT_WAIT_TIME', 300); */ class phpbb_search_fulltext_sphinx { - var $stats = array(); - var $word_length = array(); - var $split_words = array(); - var $search_query; - var $common_words = array(); - var $id; - var $indexes; - var $sphinx; + private $stats = array(); + private $split_words = array(); + private $id; + private $indexes; + private $sphinx; + public $word_length = array(); + public $search_query; + public $common_words = array(); + /** + * Constructor + * Creates a new phpbb_search_fulltext_postgres, which is used as a search backend. + * + * @param string|bool $error Any error that occurs is passed on through this reference variable otherwise false + */ public function __construct(&$error) { global $config; @@ -81,6 +87,8 @@ class phpbb_search_fulltext_sphinx * Returns the name of this search backend to be displayed to administrators * * @return string Name + * + * @access public */ public function get_name() { @@ -89,6 +97,10 @@ class phpbb_search_fulltext_sphinx /** * Checks permissions and paths, if everything is correct it generates the config file + * + * @return string|bool Language key of the error/incompatiblity occured + * + * @access public */ function init() { @@ -110,6 +122,13 @@ class phpbb_search_fulltext_sphinx return false; } + /** + * Updates the config file sphinx.conf and generates the same in case autoconf is selected + * + * @return string|bool Language key of the error/incompatiblity occured otherwise false + * + * @access private + */ function config_updated() { global $db, $user, $config, $phpbb_root_path, $phpEx; @@ -378,6 +397,8 @@ class phpbb_search_fulltext_sphinx * @param string $keywords Contains the keyword as entered by the user * @param string $terms is either 'all' or 'any' * @return false if no valid keywords were found and otherwise true + * + * @access public */ function split_keywords(&$keywords, $terms) { @@ -619,14 +640,14 @@ class phpbb_search_fulltext_sphinx /** * Updates wordlist and wordmatch tables when a message is posted or changed * - * @param string $mode Contains the post mode: edit, post, reply, quote - * @param int $post_id The id of the post which is modified/created - * @param string &$message New or updated post content - * @param string &$subject New or updated post subject - * @param int $poster_id Post author's user id - * @param int $forum_id The id of the forum in which the post is located + * @param string $mode Contains the post mode: edit, post, reply, quote + * @param int $post_id The id of the post which is modified/created + * @param string &$message New or updated post content + * @param string &$subject New or updated post subject + * @param int $poster_id Post author's user id + * @param int $forum_id The id of the forum in which the post is located * - * @access public + * @access public */ function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) { @@ -686,6 +707,8 @@ class phpbb_search_fulltext_sphinx /** * Delete a post from the index after it was deleted + * + * @access public */ function index_remove($post_ids, $author_ids, $forum_ids) { @@ -700,6 +723,8 @@ class phpbb_search_fulltext_sphinx /** * Destroy old cache entries + * + * @access public */ function tidy($create = false) { @@ -724,6 +749,10 @@ class phpbb_search_fulltext_sphinx /** * Create sphinx table + * + * @return string|bool error string is returned incase of errors otherwise false + * + * @access public */ function create_index($acp_module, $u_action) { @@ -758,6 +787,10 @@ class phpbb_search_fulltext_sphinx /** * Drop sphinx table + * + * @return string|bool error string is returned incase of errors otherwise false + * + * @access public */ function delete_index($acp_module, $u_action) { @@ -785,6 +818,10 @@ class phpbb_search_fulltext_sphinx /** * Returns true if the sphinx table was created + * + * @return bool true if sphinx table was created + * + * @access public */ function index_created($allow_new_files = true) { @@ -817,6 +854,8 @@ class phpbb_search_fulltext_sphinx /** * Kills the searchd process and makes sure there's no locks left over + * + * @access private */ function shutdown_searchd() { @@ -846,6 +885,8 @@ class phpbb_search_fulltext_sphinx * files by calling shutdown_searchd. * * @return boolean Whether searchd is running or not + * + * @access private */ function searchd_running() { @@ -890,6 +931,10 @@ class phpbb_search_fulltext_sphinx /** * Returns an associative array containing information about the indexes + * + * @return string|bool Language string of error false otherwise + * + * @access public */ function index_stats() { @@ -910,6 +955,8 @@ class phpbb_search_fulltext_sphinx /** * Collects stats that can be displayed on the index maintenance page + * + * @access private */ function get_stats() { @@ -957,6 +1004,10 @@ class phpbb_search_fulltext_sphinx /** * Returns a list of options for the ACP to display + * + * @return associative array containing template and config variables + * + * @access public */ function acp() { @@ -1112,6 +1163,8 @@ class phpbb_search_fulltext_sphinx * * @param string $path Path from which files shall be deleted * @param string $pattern PCRE pattern that a file needs to match in order to be deleted +* +* @access private */ function sphinx_unlink_by_pattern($path, $pattern) { @@ -1132,6 +1185,8 @@ function sphinx_unlink_by_pattern($path, $pattern) * @param string $file The filename from which the lines shall be read * @param int $amount The number of lines to be read from the end * @return string Last lines of the file +* +* @access private */ function sphinx_read_last_lines($file, $amount) { From e486f4389c99c27cb723d4e9fd437130752f891e Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 10 Jul 2012 03:51:40 +0530 Subject: [PATCH 0246/1142] [feature/sphinx-fulltext-search] remove autoconf Remove all code related to sphinx automatic configuration and all exec calls. PHPBB3-10946 --- phpBB/includes/search/fulltext_sphinx.php | 386 +--------------------- 1 file changed, 1 insertion(+), 385 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 82addca28a..984bda68c2 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -24,9 +24,6 @@ if (!defined('IN_PHPBB')) global $phpbb_root_path, $phpEx, $table_prefix; require($phpbb_root_path . "includes/sphinxapi-0.9.8." . $phpEx); -define('INDEXER_NAME', 'indexer'); -define('SEARCHD_NAME', 'searchd'); - define('MAX_MATCHES', 20000); define('CONNECT_RETRIES', 3); define('CONNECT_WAIT_TIME', 300); @@ -64,15 +61,6 @@ class phpbb_search_fulltext_sphinx if (!empty($config['fulltext_sphinx_configured'])) { - if ($config['fulltext_sphinx_autorun'] && !file_exists($config['fulltext_sphinx_data_path'] . 'searchd.pid') && $this->index_created(true)) - { - $this->shutdown_searchd(); -// $cwd = getcwd(); -// chdir($config['fulltext_sphinx_bin_path']); - exec($config['fulltext_sphinx_bin_path'] . SEARCHD_NAME . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf >> ' . $config['fulltext_sphinx_data_path'] . 'log/searchd-startup.log 2>&1 &'); -// chdir($cwd); - } - // we only support localhost for now $this->sphinx->SetServer('localhost', (isset($config['fulltext_sphinx_port']) && $config['fulltext_sphinx_port']) ? (int) $config['fulltext_sphinx_port'] : 3312); } @@ -133,111 +121,10 @@ class phpbb_search_fulltext_sphinx { global $db, $user, $config, $phpbb_root_path, $phpEx; - if ($config['fulltext_sphinx_autoconf']) - { - $paths = array('fulltext_sphinx_bin_path', 'fulltext_sphinx_config_path', 'fulltext_sphinx_data_path'); - - // check for completeness and add trailing slash if it's not present - foreach ($paths as $path) - { - if (empty($config[$path])) - { - return $user->lang['FULLTEXT_SPHINX_UNCONFIGURED']; - } - if ($config[$path] && substr($config[$path], -1) != '/') - { - set_config($path, $config[$path] . '/'); - } - } - } - - $executables = array( - $config['fulltext_sphinx_bin_path'] . INDEXER_NAME, - $config['fulltext_sphinx_bin_path'] . SEARCHD_NAME, - ); - - if ($config['fulltext_sphinx_autorun']) - { - foreach ($executables as $executable) - { - if (!file_exists($executable)) - { - return sprintf($user->lang['FULLTEXT_SPHINX_FILE_NOT_FOUND'], $executable); - } - - if (!function_exists('exec')) - { - return $user->lang['FULLTEXT_SPHINX_REQUIRES_EXEC']; - } - - $output = array(); - @exec($executable, $output); - - $output = implode("\n", $output); - if (strpos($output, 'Sphinx ') === false) - { - return sprintf($user->lang['FULLTEXT_SPHINX_FILE_NOT_EXECUTABLE'], $executable); - } - } - } - - $writable_paths = array( - $config['fulltext_sphinx_config_path'] => array('config' => 'fulltext_sphinx_autoconf', 'subdir' => false), - $config['fulltext_sphinx_data_path'] => array('config' => 'fulltext_sphinx_autorun', 'subdir' => 'log'), - $config['fulltext_sphinx_data_path'] . 'log/' => array('config' => 'fulltext_sphinx_autorun', 'subdir' => false), - ); - - foreach ($writable_paths as $path => $info) - { - if ($config[$info['config']]) - { - // make sure directory exists - // if we could drop the @ here and figure out whether the file really - // doesn't exist or whether open_basedir is in effect, would be nice - if (!@file_exists($path)) - { - return sprintf($user->lang['FULLTEXT_SPHINX_DIRECTORY_NOT_FOUND'], $path); - } - - // now check if it is writable by storing a simple file - $filename = $path . 'write_test'; - $fp = @fopen($filename, 'wb'); - if ($fp === false) - { - return sprintf($user->lang['FULLTEXT_SPHINX_FILE_NOT_WRITABLE'], $filename); - } - @fclose($fp); - - @unlink($filename); - - if ($info['subdir'] !== false) - { - if (!is_dir($path . $info['subdir'])) - { - mkdir($path . $info['subdir']); - } - } - } - } - - if ($config['fulltext_sphinx_autoconf']) - { include ($phpbb_root_path . 'config.' . $phpEx); // now that we're sure everything was entered correctly, generate a config for the index // we misuse the avatar_salt for this, as it should be unique ;-) - - if (!file_exists($config['fulltext_sphinx_config_path'] . 'sphinx.conf')) - { - $filename = $config['fulltext_sphinx_config_path'] . 'sphinx.conf'; - $fp = @fopen($filename, 'wb'); - if ($fp === false) - { - return sprintf($user->lang['FULLTEXT_SPHINX_FILE_NOT_WRITABLE'], $filename); - } - @fclose($fp); - } - $config_object = new phpbb_search_sphinx_config($config['fulltext_sphinx_config_path'] . 'sphinx.conf'); $config_data = array( @@ -379,12 +266,8 @@ class phpbb_search_fulltext_sphinx } } - $config_object->write($config['fulltext_sphinx_config_path'] . 'sphinx.conf'); - } - set_config('fulltext_sphinx_configured', '1'); - $this->shutdown_searchd(); $this->tidy(); return false; @@ -689,20 +572,6 @@ class phpbb_search_fulltext_sphinx $this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates); } } - - if ($config['fulltext_sphinx_autorun']) - { - if ($this->index_created()) - { - $rotate = ($this->searchd_running()) ? ' --rotate' : ''; - - $cwd = getcwd(); - chdir($config['fulltext_sphinx_bin_path']); - exec('./' . INDEXER_NAME . $rotate . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf index_phpbb_' . $this->id . '_delta >> ' . $config['fulltext_sphinx_data_path'] . 'log/indexer.log 2>&1 &'); - var_dump('./' . INDEXER_NAME . $rotate . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf index_phpbb_' . $this->id . '_delta >> ' . $config['fulltext_sphinx_data_path'] . 'log/indexer.log 2>&1 &'); - chdir($cwd); - } - } } /** @@ -730,20 +599,6 @@ class phpbb_search_fulltext_sphinx { global $config; - if ($config['fulltext_sphinx_autorun']) - { - if ($this->index_created() || $create) - { - $rotate = ($this->searchd_running()) ? ' --rotate' : ''; - - $cwd = getcwd(); - chdir($config['fulltext_sphinx_bin_path']); - exec('./' . INDEXER_NAME . $rotate . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf index_phpbb_' . $this->id . '_main >> ' . $config['fulltext_sphinx_data_path'] . 'log/indexer.log 2>&1 &'); - exec('./' . INDEXER_NAME . $rotate . ' --config ' . $config['fulltext_sphinx_config_path'] . 'sphinx.conf index_phpbb_' . $this->id . '_delta >> ' . $config['fulltext_sphinx_data_path'] . 'log/indexer.log 2>&1 &'); - chdir($cwd); - } - } - set_config('search_last_gc', time(), true); } @@ -758,8 +613,6 @@ class phpbb_search_fulltext_sphinx { global $db, $user, $config; - $this->shutdown_searchd(); - if (!isset($config['fulltext_sphinx_configured']) || !$config['fulltext_sphinx_configured']) { return $user->lang['FULLTEXT_SPHINX_CONFIGURE_FIRST']; @@ -780,8 +633,6 @@ class phpbb_search_fulltext_sphinx // start indexing process $this->tidy(true); - $this->shutdown_searchd(); - return false; } @@ -796,13 +647,6 @@ class phpbb_search_fulltext_sphinx { global $db, $config; - $this->shutdown_searchd(); - - if ($config['fulltext_sphinx_autorun']) - { - sphinx_unlink_by_pattern($config['fulltext_sphinx_data_path'], '#^index_phpbb_' . $this->id . '.*$#'); - } - if (!$this->index_created()) { return false; @@ -811,8 +655,6 @@ class phpbb_search_fulltext_sphinx $sql = 'DROP TABLE ' . SPHINX_TABLE; $db->sql_query($sql); - $this->shutdown_searchd(); - return false; } @@ -836,99 +678,12 @@ class phpbb_search_fulltext_sphinx if ($row) { - if ($config['fulltext_sphinx_autorun']) - { - if ((file_exists($config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main.spd') && file_exists($config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta.spd')) || ($allow_new_files && file_exists($config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main.new.spd') && file_exists($config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta.new.spd'))) - { - $created = true; - } - } - else - { - $created = true; - } + $created = true; } return $created; } - /** - * Kills the searchd process and makes sure there's no locks left over - * - * @access private - */ - function shutdown_searchd() - { - global $config; - - if ($config['fulltext_sphinx_autorun']) - { - if (!function_exists('exec')) - { - set_config('fulltext_sphinx_autorun', '0'); - return; - } - - exec('killall -9 ' . SEARCHD_NAME . ' >> /dev/null 2>&1 &'); - - if (file_exists($config['fulltext_sphinx_data_path'] . 'searchd.pid')) - { - unlink($config['fulltext_sphinx_data_path'] . 'searchd.pid'); - } - - sphinx_unlink_by_pattern($config['fulltext_sphinx_data_path'], '#^.*\.spl$#'); - } - } - - /** - * Checks whether searchd is running, if it's not running it makes sure there's no left over - * files by calling shutdown_searchd. - * - * @return boolean Whether searchd is running or not - * - * @access private - */ - function searchd_running() - { - global $config; - - // if we cannot manipulate the service assume it is running - if (!$config['fulltext_sphinx_autorun']) - { - return true; - } - - if (file_exists($config['fulltext_sphinx_data_path'] . 'searchd.pid')) - { - $pid = trim(file_get_contents($config['fulltext_sphinx_data_path'] . 'searchd.pid')); - - if ($pid) - { - $output = array(); - $pidof_command = 'pidof'; - - exec('whereis -b pidof', $output); - if (sizeof($output) > 1) - { - $output = explode(' ', trim($output[0])); - $pidof_command = $output[1]; // 0 is pidof: - } - - $output = array(); - exec($pidof_command . ' ' . SEARCHD_NAME, $output); - if (sizeof($output) && (trim($output[0]) == $pid || trim($output[1]) == $pid)) - { - return true; - } - } - } - - // make sure it's really not running - $this->shutdown_searchd(); - - return false; - } - /** * Returns an associative array containing information about the indexes * @@ -980,26 +735,6 @@ class phpbb_search_fulltext_sphinx } $this->stats['last_searches'] = ''; - if ($config['fulltext_sphinx_autorun']) - { - if (file_exists($config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log')) - { - $last_searches = explode("\n", utf8_htmlspecialchars(sphinx_read_last_lines($config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log', 3))); - - foreach($last_searches as $i => $search) - { - if (strpos($search, '[' . $this->indexes . ']') !== false) - { - $last_searches[$i] = str_replace('[' . $this->indexes . ']', '', $search); - } - else - { - $last_searches[$i] = ''; - } - } - $this->stats['last_searches'] = implode("\n", $last_searches); - } - } } /** @@ -1014,8 +749,6 @@ class phpbb_search_fulltext_sphinx global $user, $config; $config_vars = array( - 'fulltext_sphinx_autoconf' => 'bool', - 'fulltext_sphinx_autorun' => 'bool', 'fulltext_sphinx_config_path' => 'string', 'fulltext_sphinx_data_path' => 'string', 'fulltext_sphinx_bin_path' => 'string', @@ -1025,8 +758,6 @@ class phpbb_search_fulltext_sphinx ); $defaults = array( - 'fulltext_sphinx_autoconf' => '1', - 'fulltext_sphinx_autorun' => '1', 'fulltext_sphinx_indexer_mem_limit' => '512', ); @@ -1043,57 +774,8 @@ class phpbb_search_fulltext_sphinx } } - $no_autoconf = false; - $no_autorun = false; $bin_path = $config['fulltext_sphinx_bin_path']; - // try to guess the path if it is empty - if (empty($bin_path)) - { - if (@file_exists('/usr/local/bin/' . INDEXER_NAME) && @file_exists('/usr/local/bin/' . SEARCHD_NAME)) - { - $bin_path = '/usr/local/bin/'; - } - else if (@file_exists('/usr/bin/' . INDEXER_NAME) && @file_exists('/usr/bin/' . SEARCHD_NAME)) - { - $bin_path = '/usr/bin/'; - } - else - { - $output = array(); - if (!function_exists('exec') || null === @exec('whereis -b ' . INDEXER_NAME, $output)) - { - $no_autorun = true; - } - else if (sizeof($output)) - { - $output = explode(' ', $output[0]); - array_shift($output); // remove indexer: - - foreach ($output as $path) - { - $path = dirname($path) . '/'; - - if (file_exists($path . INDEXER_NAME) && file_exists($path . SEARCHD_NAME)) - { - $bin_path = $path; - break; - } - } - } - } - } - - if ($no_autorun) - { - set_config('fulltext_sphinx_autorun', '0'); - } - - if ($no_autoconf) - { - set_config('fulltext_sphinx_autoconf', '0'); - } - // rewrite config if fulltext sphinx is enabled if ($config['fulltext_sphinx_autoconf'] && isset($config['fulltext_sphinx_configured']) && $config['fulltext_sphinx_configured']) { @@ -1115,14 +797,6 @@ class phpbb_search_fulltext_sphinx $tpl = ' ' . $user->lang['FULLTEXT_SPHINX_CONFIGURE_BEFORE']. ' -
-

' . $user->lang['FULLTEXT_SPHINX_AUTOCONF_EXPLAIN'] . '
-
-
-
-

' . $user->lang['FULLTEXT_SPHINX_AUTORUN_EXPLAIN'] . '
-
-

' . $user->lang['FULLTEXT_SPHINX_CONFIG_PATH_EXPLAIN'] . '
@@ -1157,61 +831,3 @@ class phpbb_search_fulltext_sphinx ); } } - -/** -* Deletes all files from a directory that match a certain pattern -* -* @param string $path Path from which files shall be deleted -* @param string $pattern PCRE pattern that a file needs to match in order to be deleted -* -* @access private -*/ -function sphinx_unlink_by_pattern($path, $pattern) -{ - $dir = opendir($path); - while (false !== ($file = readdir($dir))) - { - if (is_file($path . $file) && preg_match($pattern, $file)) - { - unlink($path . $file); - } - } - closedir($dir); -} - -/** -* Reads the last from a file -* -* @param string $file The filename from which the lines shall be read -* @param int $amount The number of lines to be read from the end -* @return string Last lines of the file -* -* @access private -*/ -function sphinx_read_last_lines($file, $amount) -{ - $fp = fopen($file, 'r'); - fseek($fp, 0, SEEK_END); - - $c = ''; - $i = 0; - - while ($i < $amount) - { - fseek($fp, -2, SEEK_CUR); - $c = fgetc($fp); - if ($c == "\n") - { - $i++; - } - if (feof($fp)) - { - break; - } - } - - $string = fread($fp, 8192); - fclose($fp); - - return $string; -} From 88089194e570edb77240138695034358062ffa58 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 10 Jul 2012 03:55:23 +0530 Subject: [PATCH 0247/1142] [feature/sphinx-fulltext-search] prefix sphinx with constant names All constant names are prefixed with SPHINX_ PHPBB3-10946 --- phpBB/includes/search/fulltext_sphinx.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 984bda68c2..7386833151 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -24,9 +24,9 @@ if (!defined('IN_PHPBB')) global $phpbb_root_path, $phpEx, $table_prefix; require($phpbb_root_path . "includes/sphinxapi-0.9.8." . $phpEx); -define('MAX_MATCHES', 20000); -define('CONNECT_RETRIES', 3); -define('CONNECT_WAIT_TIME', 300); +define('SPHINX_MAX_MATCHES', 20000); +define('SPHINX_CONNECT_RETRIES', 3); +define('SPHINX_CONNECT_WAIT_TIME', 300); /** * fulltext_sphinx @@ -216,7 +216,7 @@ class phpbb_search_fulltext_sphinx array('read_timeout', '5'), array('max_children', '30'), array('pid_file', $config['fulltext_sphinx_data_path'] . "searchd.pid"), - array('max_matches', (string) MAX_MATCHES), + array('max_matches', (string) SPHINX_MAX_MATCHES), array('binlog_path', $config['fulltext_sphinx_data_path']), ), ); @@ -451,14 +451,14 @@ class phpbb_search_fulltext_sphinx $this->sphinx->SetFilter('deleted', array(0)); - $this->sphinx->SetLimits($start, (int) $per_page, MAX_MATCHES); + $this->sphinx->SetLimits($start, (int) $per_page, SPHINX_MAX_MATCHES); $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); // could be connection to localhost:3312 failed (errno=111, msg=Connection refused) during rotate, retry if so - $retries = CONNECT_RETRIES; + $retries = SPHINX_CONNECT_RETRIES; while (!$result && (strpos($this->sphinx->_error, "errno=111,") !== false) && $retries--) { - usleep(CONNECT_WAIT_TIME); + usleep(SPHINX_CONNECT_WAIT_TIME); $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); } From 569db1471b3000512232732a790d8653250e8012 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 10 Jul 2012 04:15:35 +0530 Subject: [PATCH 0248/1142] [feature/sphinx-fulltext-search] fix stopwords option Stopwords option can be configured in ACP to generate correct sphinx config file. PHPBB3-10946 --- phpBB/includes/search/fulltext_sphinx.php | 28 ++++------------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 7386833151..710c3d56be 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -193,7 +193,7 @@ class phpbb_search_fulltext_sphinx array('source', 'source_phpbb_' . $this->id . '_main'), array('docinfo', 'extern'), array('morphology', 'none'), - array('stopwords', (file_exists($config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt') && $config['fulltext_sphinx_stopwords']) ? $config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt' : ''), + array('stopwords', $config['fulltext_sphinx_stopwords'] ? $config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt' : ''), array('min_word_len', '2'), array('charset_type', 'utf-8'), array('charset_table', 'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'), @@ -759,6 +759,7 @@ class phpbb_search_fulltext_sphinx $defaults = array( 'fulltext_sphinx_indexer_mem_limit' => '512', + 'fulltext_sphinx_stopwords' => '0', ); foreach ($config_vars as $config_var => $type) @@ -774,27 +775,6 @@ class phpbb_search_fulltext_sphinx } } - $bin_path = $config['fulltext_sphinx_bin_path']; - - // rewrite config if fulltext sphinx is enabled - if ($config['fulltext_sphinx_autoconf'] && isset($config['fulltext_sphinx_configured']) && $config['fulltext_sphinx_configured']) - { - $this->config_updated(); - } - - // check whether stopwords file is available and enabled - if (@file_exists($config['fulltext_sphinx_config_path'] . 'sphinx_stopwords.txt')) - { - $stopwords_available = true; - $stopwords_active = $config['fulltext_sphinx_stopwords']; - } - else - { - $stopwords_available = false; - $stopwords_active = false; - set_config('fulltext_sphinx_stopwords', '0'); - } - $tpl = ' ' . $user->lang['FULLTEXT_SPHINX_CONFIGURE_BEFORE']. '
@@ -809,11 +789,11 @@ class phpbb_search_fulltext_sphinx

' . $user->lang['FULLTEXT_SPHINX_DATA_PATH_EXPLAIN'] . '
- ' . $user->lang['FULLTEXT_SPHINX_CONFIGURE_AFTER']. '

' . $user->lang['FULLTEXT_SPHINX_STOPWORDS_FILE_EXPLAIN'] . '
-
+
+ ' . $user->lang['FULLTEXT_SPHINX_CONFIGURE_AFTER']. '

' . $user->lang['FULLTEXT_SPHINX_PORT_EXPLAIN'] . '
From d2e42d7d619100695e0efe8d472c71f61cbfcb45 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 10 Jul 2012 04:53:51 +0530 Subject: [PATCH 0249/1142] [feature/sphinx-fulltext-search] remove unnecessary code Some extra conditions and variables used in autoconf are removed. PHPBB3-10946 --- phpBB/includes/search/fulltext_sphinx.php | 25 +++++------------------ 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 710c3d56be..48855ef7d8 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -59,11 +59,8 @@ class phpbb_search_fulltext_sphinx $this->sphinx = new SphinxClient(); - if (!empty($config['fulltext_sphinx_configured'])) - { - // we only support localhost for now - $this->sphinx->SetServer('localhost', (isset($config['fulltext_sphinx_port']) && $config['fulltext_sphinx_port']) ? (int) $config['fulltext_sphinx_port'] : 3312); - } + // We only support localhost for now + $this->sphinx->SetServer('localhost', (isset($config['fulltext_sphinx_port']) && $config['fulltext_sphinx_port']) ? (int) $config['fulltext_sphinx_port'] : 3312); $config['fulltext_sphinx_min_word_len'] = 2; $config['fulltext_sphinx_max_word_len'] = 400; @@ -125,7 +122,7 @@ class phpbb_search_fulltext_sphinx // now that we're sure everything was entered correctly, generate a config for the index // we misuse the avatar_salt for this, as it should be unique ;-) - $config_object = new phpbb_search_sphinx_config($config['fulltext_sphinx_config_path'] . 'sphinx.conf'); + $config_object = new phpbb_search_sphinx_config($config['fulltext_sphinx_config_path'] . 'sphinx.conf'); $config_data = array( 'source source_phpbb_' . $this->id . '_main' => array( @@ -266,10 +263,6 @@ class phpbb_search_fulltext_sphinx } } - set_config('fulltext_sphinx_configured', '1'); - - $this->tidy(); - return false; } @@ -613,11 +606,6 @@ class phpbb_search_fulltext_sphinx { global $db, $user, $config; - if (!isset($config['fulltext_sphinx_configured']) || !$config['fulltext_sphinx_configured']) - { - return $user->lang['FULLTEXT_SPHINX_CONFIGURE_FIRST']; - } - if (!$this->index_created()) { $sql = 'CREATE TABLE IF NOT EXISTS ' . SPHINX_TABLE . ' ( @@ -630,9 +618,6 @@ class phpbb_search_fulltext_sphinx $db->sql_query($sql); } - // start indexing process - $this->tidy(true); - return false; } @@ -645,7 +630,7 @@ class phpbb_search_fulltext_sphinx */ function delete_index($acp_module, $u_action) { - global $db, $config; + global $db; if (!$this->index_created()) { @@ -715,7 +700,7 @@ class phpbb_search_fulltext_sphinx */ function get_stats() { - global $db, $config; + global $db; if ($this->index_created()) { From d908d932736775b0274564dbc6206babf64ba1be Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 12 Jul 2012 16:02:58 -0400 Subject: [PATCH 0250/1142] [ticket/10444] Do not default to the previous post edit reason. PHPBB3-10444 --- phpBB/posting.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/posting.php b/phpBB/posting.php index 7f57f693af..558520dbaa 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -1376,7 +1376,7 @@ $template->assign_vars(array( 'POST_DATE' => ($post_data['post_time']) ? $user->format_date($post_data['post_time']) : '', 'ERROR' => (sizeof($error)) ? implode('
', $error) : '', 'TOPIC_TIME_LIMIT' => (int) $post_data['topic_time_limit'], - 'EDIT_REASON' => $post_data['post_edit_reason'], + 'EDIT_REASON' => $request->variable('edit_reason', ''), 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id"), 'U_VIEW_TOPIC' => ($mode != 'post') ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id") : '', 'U_PROGRESS_BAR' => append_sid("{$phpbb_root_path}posting.$phpEx", "f=$forum_id&mode=popup"), From f3e6547acf15bc7310b0c1cd7695253ed01f694c Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 13 Jul 2012 22:18:28 -0500 Subject: [PATCH 0251/1142] [ticket/7598] Inactive users action notification (ACP) When performing an action on users in the ACP Inactive Users, such as activating, deleting, or reminding, trigger_error is now called to give a notification that the action was performed PHPBB3-7598 --- phpBB/includes/acp/acp_inactive.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index 78d6a0b2f3..6d14365ce9 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -136,6 +136,8 @@ class acp_inactive add_log('admin', 'LOG_USER_ACTIVE', $row['username']); add_log('user', $row['user_id'], 'LOG_USER_ACTIVE_USER'); } + + trigger_error(sprintf($user->lang['LOG_INACTIVE_ACTIVATE'], implode(', ', $user_affected) . ' ' . adm_back_link($this->u_action))); } // For activate we really need to redirect, else a refresh can result in users being deactivated again @@ -159,6 +161,8 @@ class acp_inactive } add_log('admin', 'LOG_INACTIVE_' . strtoupper($action), implode(', ', $user_affected)); + + trigger_error(sprintf($user->lang['LOG_INACTIVE_DELETE'], implode(', ', $user_affected) . ' ' . adm_back_link($this->u_action))); } else { @@ -230,7 +234,8 @@ class acp_inactive $db->sql_query($sql); add_log('admin', 'LOG_INACTIVE_REMIND', implode(', ', $usernames)); - unset($usernames); + + trigger_error(sprintf($user->lang['LOG_INACTIVE_REMIND'], implode(', ', $usernames) . ' ' . adm_back_link($this->u_action))); } $db->sql_freeresult($result); From 97350944e3bcc4042ff030af4eb130facdd253c1 Mon Sep 17 00:00:00 2001 From: Nathan Date: Sat, 14 Jul 2012 17:40:37 -0500 Subject: [PATCH 0252/1142] [ticket/7598] Use $user->lang['COMMA_SEPARATOR'] PHPBB3-7598 --- phpBB/includes/acp/acp_inactive.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index 6d14365ce9..f098b772ee 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -137,7 +137,7 @@ class acp_inactive add_log('user', $row['user_id'], 'LOG_USER_ACTIVE_USER'); } - trigger_error(sprintf($user->lang['LOG_INACTIVE_ACTIVATE'], implode(', ', $user_affected) . ' ' . adm_back_link($this->u_action))); + trigger_error(sprintf($user->lang['LOG_INACTIVE_ACTIVATE'], implode($user->lang['COMMA_SEPARATOR'], $user_affected) . ' ' . adm_back_link($this->u_action))); } // For activate we really need to redirect, else a refresh can result in users being deactivated again @@ -162,7 +162,7 @@ class acp_inactive add_log('admin', 'LOG_INACTIVE_' . strtoupper($action), implode(', ', $user_affected)); - trigger_error(sprintf($user->lang['LOG_INACTIVE_DELETE'], implode(', ', $user_affected) . ' ' . adm_back_link($this->u_action))); + trigger_error(sprintf($user->lang['LOG_INACTIVE_DELETE'], implode($user->lang['COMMA_SEPARATOR'], $user_affected) . ' ' . adm_back_link($this->u_action))); } else { @@ -235,7 +235,7 @@ class acp_inactive add_log('admin', 'LOG_INACTIVE_REMIND', implode(', ', $usernames)); - trigger_error(sprintf($user->lang['LOG_INACTIVE_REMIND'], implode(', ', $usernames) . ' ' . adm_back_link($this->u_action))); + trigger_error(sprintf($user->lang['LOG_INACTIVE_REMIND'], implode($user->lang['COMMA_SEPARATOR'], $usernames) . ' ' . adm_back_link($this->u_action))); } $db->sql_freeresult($result); From 1b826842aadc16ad35485479f4bb3bdef03b534c Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 16 Jul 2012 16:59:40 +0200 Subject: [PATCH 0253/1142] [ticket/10942] Avoid possible conflicts with magic words in unit tests PHPBB3-10942 --- tests/dbal/case_test.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/dbal/case_test.php b/tests/dbal/case_test.php index 5219defa06..57a1729a39 100644 --- a/tests/dbal/case_test.php +++ b/tests/dbal/case_test.php @@ -18,52 +18,52 @@ class phpbb_dbal_case_test extends phpbb_database_test_case { $db = $this->new_dbal(); - $sql = 'SELECT ' . $db->sql_case('1 = 1', '1', '2') . ' AS num + $sql = 'SELECT ' . $db->sql_case('1 = 1', '1', '2') . ' AS test_num FROM phpbb_config'; $result = $db->sql_query_limit($sql, 1); - $this->assertEquals(1, (int) $db->sql_fetchfield('num')); + $this->assertEquals(1, (int) $db->sql_fetchfield('test_num')); - $sql = 'SELECT ' . $db->sql_case('1 = 0', '1', '2') . ' AS num + $sql = 'SELECT ' . $db->sql_case('1 = 0', '1', '2') . ' AS test_num FROM phpbb_config'; $result = $db->sql_query_limit($sql, 1); - $this->assertEquals(2, (int) $db->sql_fetchfield('num')); + $this->assertEquals(2, (int) $db->sql_fetchfield('test_num')); } public function test_case_string() { $db = $this->new_dbal(); - $sql = 'SELECT ' . $db->sql_case('1 = 1', "'foo'", "'bar'") . ' AS string + $sql = 'SELECT ' . $db->sql_case('1 = 1', "'foo'", "'bar'") . ' AS test_string FROM phpbb_config'; $result = $db->sql_query_limit($sql, 1); - $this->assertEquals('foo', $db->sql_fetchfield('string')); + $this->assertEquals('foo', $db->sql_fetchfield('test_string')); - $sql = 'SELECT ' . $db->sql_case('1 = 0', "'foo'", "'bar'") . ' AS string + $sql = 'SELECT ' . $db->sql_case('1 = 0', "'foo'", "'bar'") . ' AS test_string FROM phpbb_config'; $result = $db->sql_query_limit($sql, 1); - $this->assertEquals('bar', $db->sql_fetchfield('string')); + $this->assertEquals('bar', $db->sql_fetchfield('test_string')); } public function test_case_column() { $db = $this->new_dbal(); - $sql = 'SELECT ' . $db->sql_case("config_name = 'config1'", 'config_name', 'config_value') . " AS string + $sql = 'SELECT ' . $db->sql_case("config_name = 'config1'", 'config_name', 'config_value') . " AS test_string FROM phpbb_config WHERE config_name = 'config1'"; $result = $db->sql_query_limit($sql, 1); - $this->assertEquals('config1', $db->sql_fetchfield('string')); + $this->assertEquals('config1', $db->sql_fetchfield('test_string')); - $sql = 'SELECT ' . $db->sql_case("config_name = 'config1'", 'config_name', 'config_value') . " AS string + $sql = 'SELECT ' . $db->sql_case("config_name = 'config1'", 'config_name', 'config_value') . " AS test_string FROM phpbb_config WHERE config_value = 'bar'"; $result = $db->sql_query_limit($sql, 1); - $this->assertEquals('bar', $db->sql_fetchfield('string')); + $this->assertEquals('bar', $db->sql_fetchfield('test_string')); } } From d9a32ce6143be27b8414072694c969fa16b5329a Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 16 Jul 2012 17:22:10 +0200 Subject: [PATCH 0254/1142] [ticket/10950] Update undelivered pm counts in batches not 1 by 1 for each user PHPBB3-10950 --- phpBB/includes/functions_privmsgs.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php index 1d45961ac4..82344d1787 100644 --- a/phpBB/includes/functions_privmsgs.php +++ b/phpBB/includes/functions_privmsgs.php @@ -1175,16 +1175,29 @@ function phpbb_delete_user_pms($user_id) while ($row = $db->sql_fetchrow($result)) { - $undelivered_user[$row['user_id']] = (int) $row['num_undelivered_privmsgs']; + $num_pms = (int) $row['num_undelivered_privmsgs']; + $undelivered_user[$num_pms][] = (int) $row['user_id']; + + if (sizeof($undelivered_user[$num_pms]) > 50) + { + // If there are too many users affected the query might get + // too long, so we update the value for the first bunch here. + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_new_privmsg = user_new_privmsg - ' . $num_pms . ', + user_unread_privmsg = user_unread_privmsg - ' . $num_pms . ' + WHERE ' . $db->sql_in_set('user_id', $undelivered_user[$num_pms]); + $db->sql_query($sql); + unset($undelivered_user[$num_pms]); + } } $db->sql_freeresult($result); - foreach ($undelivered_user as $undelivered_user_id => $count) + foreach ($undelivered_user as $num_pms => $undelivered_user_set) { $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_new_privmsg = user_new_privmsg - ' . $count . ', - user_unread_privmsg = user_unread_privmsg - ' . $count . ' - WHERE user_id = ' . $undelivered_user_id; + SET user_new_privmsg = user_new_privmsg - ' . $num_pms . ', + user_unread_privmsg = user_unread_privmsg - ' . $num_pms . ' + WHERE ' . $db->sql_in_set('user_id', $undelivered_user_set); $db->sql_query($sql); } From 91050356d3b8322098650bf660c07f8a49ddb02d Mon Sep 17 00:00:00 2001 From: Fyorl Date: Thu, 12 Jul 2012 18:38:20 +0100 Subject: [PATCH 0255/1142] [ticket/10992] Modified upload tests to work with new version PHPBB3-10992 --- tests/functional/fileupload_form_test.php | 41 +++++++++++++---------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/tests/functional/fileupload_form_test.php b/tests/functional/fileupload_form_test.php index 6ba55eeba7..9c7bed359c 100644 --- a/tests/functional/fileupload_form_test.php +++ b/tests/functional/fileupload_form_test.php @@ -22,41 +22,48 @@ class phpbb_functional_fileupload_form_test extends phpbb_functional_test_case $this->login(); } + private function upload_file($filename, $mimetype) + { + $file = array( + 'tmp_name' => $this->path . $filename, + 'name' => $filename, + 'type' => $mimetype, + 'size' => filesize($this->path . $filename), + 'error' => UPLOAD_ERR_OK, + ); + + $crawler = $this->client->request( + 'POST', + 'posting.php?mode=reply&f=2&t=1&sid=' . $this->sid, + array('add_file' => $this->lang('ADD_FILE')), + array('fileupload' => $file) + ); + + return $crawler; + } + public function test_empty_file() { - $crawler = $this->request('GET', 'posting.php?mode=reply&f=2&t=1&sid=' . $this->sid); - $form = $crawler->selectButton('add_file')->form(); - $form['fileupload']->upload($this->path . 'empty.png'); - $crawler = $this->client->submit($form); + $crawler = $this->upload_file('empty.jpg', 'image/jpeg'); $this->assertEquals($this->lang('ATTACHED_IMAGE_NOT_IMAGE'), $crawler->filter('div#message p')->text()); } public function test_invalid_extension() { - $crawler = $this->request('GET', 'posting.php?mode=reply&f=2&t=1&sid=' . $this->sid); - $form = $crawler->selectButton('add_file')->form(); - $form['fileupload']->upload($this->path . 'illegal-extension.bif'); - $crawler = $this->client->submit($form); + $crawler = $this->upload_file('illegal-extension.bif', 'application/octetstream'); $this->assertEquals($this->lang('DISALLOWED_EXTENSION', 'bif'), $crawler->filter('p.error')->text()); } public function test_too_large() { $this->markTestIncomplete('Functional tests use an admin account which ignores maximum upload size.'); - $crawler = $this->request('GET', 'posting.php?mode=reply&f=2&t=1&sid=' . $this->sid); - $form = $crawler->selectButton('add_file')->form(); - $form['fileupload']->upload($this->path . 'too-large.png'); - $crawler = $this->client->submit($form); + $crawler = $this->upload_file('too-large.png', 'image/png'); $this->assertEquals($this->lang('WRONG_FILESIZE', '256', 'KiB'), $crawler->filter('p.error')->text()); } public function test_valid_file() { - $crawler = $this->request('GET', 'posting.php?mode=reply&f=2&t=1&sid=' . $this->sid); - $form = $crawler->selectButton('add_file')->form(); - $form['fileupload']->upload($this->path . 'valid.jpg'); - $crawler = $this->client->submit($form); - $this->assertEquals(0, $crawler->filter('p.error')->count()); + $crawler = $this->upload_file('valid.jpg', 'image/jpeg'); $this->assertContains($this->lang('POSTED_ATTACHMENTS'), $crawler->filter('#postform h3')->eq(1)->text()); } } From e9a1189bfc75609de912bc5cb8c733f3323e58c6 Mon Sep 17 00:00:00 2001 From: Fyorl Date: Fri, 13 Jul 2012 20:43:11 +0100 Subject: [PATCH 0256/1142] [ticket/10992] test_empty_file() now tries to upload the correct file PHPBB3-10992 --- tests/functional/fileupload_form_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/fileupload_form_test.php b/tests/functional/fileupload_form_test.php index 9c7bed359c..00ff303591 100644 --- a/tests/functional/fileupload_form_test.php +++ b/tests/functional/fileupload_form_test.php @@ -44,7 +44,7 @@ class phpbb_functional_fileupload_form_test extends phpbb_functional_test_case public function test_empty_file() { - $crawler = $this->upload_file('empty.jpg', 'image/jpeg'); + $crawler = $this->upload_file('empty.png', 'image/png'); $this->assertEquals($this->lang('ATTACHED_IMAGE_NOT_IMAGE'), $crawler->filter('div#message p')->text()); } From fa4eaeb30668ada5d81aa8dcbac3246a3f59d05c Mon Sep 17 00:00:00 2001 From: Fyorl Date: Fri, 13 Jul 2012 20:09:11 +0100 Subject: [PATCH 0257/1142] [ticket/10992] Changed octetstream to octet-stream PHPBB3-10992 --- tests/functional/fileupload_form_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/fileupload_form_test.php b/tests/functional/fileupload_form_test.php index 00ff303591..f7267fa659 100644 --- a/tests/functional/fileupload_form_test.php +++ b/tests/functional/fileupload_form_test.php @@ -50,7 +50,7 @@ class phpbb_functional_fileupload_form_test extends phpbb_functional_test_case public function test_invalid_extension() { - $crawler = $this->upload_file('illegal-extension.bif', 'application/octetstream'); + $crawler = $this->upload_file('illegal-extension.bif', 'application/octet-stream'); $this->assertEquals($this->lang('DISALLOWED_EXTENSION', 'bif'), $crawler->filter('p.error')->text()); } From f5bb145040a483a76fd734bb9d4025c54e7917a0 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 16 Jul 2012 18:29:31 +0200 Subject: [PATCH 0258/1142] [feature/new-tz-handling] Require user argument on phpbb_datetime PHPBB3-9558 --- phpBB/includes/datetime.php | 7 +++---- phpBB/includes/functions.php | 4 ++-- phpBB/includes/user.php | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index 317376431d..0d80c77115 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -35,10 +35,9 @@ class phpbb_datetime extends DateTime * @param DateTimeZone $timezone Time zone of the time. * @param user User object for context. */ - public function __construct($time = 'now', DateTimeZone $timezone = null, $user = null) + public function __construct($user, $time = 'now', DateTimeZone $timezone = null) { - $this->user = $user ?: $GLOBALS['user']; - + $this->user = $user; $timezone = $timezone ?: $this->user->tz; parent::__construct($time, $timezone); @@ -56,7 +55,7 @@ class phpbb_datetime extends DateTime $format = $format ? $format : $this->user->date_format; $format = self::format_cache($format, $this->user); $relative = ($format['is_short'] && !$force_absolute); - $now = new self('now', $this->user->tz, $this->user); + $now = new self($this->user, 'now', $this->user->tz); $timestamp = $this->getTimestamp(); $now_ts = $now->getTimeStamp(); diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 98a08ea4d3..4fc2739f33 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1164,7 +1164,7 @@ function tz_select($default = '', $truncate = false, $return_tzs_only = true) foreach ($unsorted_timezones as $timezone) { $tz = new DateTimeZone($timezone); - $dt = new phpbb_datetime('now', $tz, $user); + $dt = new phpbb_datetime($user, 'now', $tz); $offset = $dt->getOffset(); $current_time = $dt->format($user->lang['DATETIME_FORMAT'], true); $offset_string = phpbb_format_timezone_offset($offset); @@ -4813,7 +4813,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 } } - $dt = new phpbb_datetime('now', $user->tz, $user); + $dt = new phpbb_datetime($user, 'now', $user->tz); $timezone_offset = 'GMT' . phpbb_format_timezone_offset($dt->getOffset()); $timezone_name = $user->tz->getName(); if (isset($user->lang['timezones'][$timezone_name])) diff --git a/phpBB/includes/user.php b/phpBB/includes/user.php index df8f048c89..cc50d2b98e 100644 --- a/phpBB/includes/user.php +++ b/phpBB/includes/user.php @@ -631,7 +631,7 @@ class phpbb_user extends phpbb_session $utc = new DateTimeZone('UTC'); } - $time = new phpbb_datetime("@$gmepoch", $utc, $this); + $time = new phpbb_datetime($this, "@$gmepoch", $utc); $time->setTimezone($this->tz); return $time->format($format, $forcedate); @@ -648,7 +648,7 @@ class phpbb_user extends phpbb_session public function create_datetime($time = 'now', DateTimeZone $timezone = null) { $timezone = $timezone ?: $this->tz; - return new phpbb_datetime($time, $timezone, $this); + return new phpbb_datetime($this, $time, $timezone); } /** From b61ac43abecafdd4e9597022fe8b09323854aac9 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 16 Jul 2012 18:41:30 +0200 Subject: [PATCH 0259/1142] [feature/new-tz-handling] Allow phpbb prefix for user data validation functions PHPBB3-9558 --- phpBB/includes/functions_user.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index f235b2be55..27f4b60189 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -1248,10 +1248,21 @@ function validate_data($data, $val_ary) $function = array_shift($validate); array_unshift($validate, $data[$var]); - if ($result = call_user_func_array('validate_' . $function, $validate)) + if (function_exists('phpbb_validate_' . $function)) { - // Since errors are checked later for their language file existence, we need to make sure custom errors are not adjusted. - $error[] = (empty($user->lang[$result . '_' . strtoupper($var)])) ? $result : $result . '_' . strtoupper($var); + if ($result = call_user_func_array('phpbb_validate_' . $function, $validate)) + { + // Since errors are checked later for their language file existence, we need to make sure custom errors are not adjusted. + $error[] = (empty($user->lang[$result . '_' . strtoupper($var)])) ? $result : $result . '_' . strtoupper($var); + } + } + else + { + if ($result = call_user_func_array('validate_' . $function, $validate)) + { + // Since errors are checked later for their language file existence, we need to make sure custom errors are not adjusted. + $error[] = (empty($user->lang[$result . '_' . strtoupper($var)])) ? $result : $result . '_' . strtoupper($var); + } } } } @@ -1407,7 +1418,7 @@ function validate_language_iso_name($lang_iso) * a string which will be used as the error message * (with the variable name appended) */ -function validate_timezone($timezone) +function phpbb_validate_timezone($timezone) { return (in_array($timezone, DateTimeZone::listIdentifiers())) ? false : 'TIMEZONE_INVALID'; } From 48d3c68290b96c7d2a0e875ec3338f067a529b22 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 16 Jul 2012 18:44:13 +0200 Subject: [PATCH 0260/1142] [feature/new-tz-handling] Use tmp variable for user timezone PHPBB3-9558 --- phpBB/includes/user.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/user.php b/phpBB/includes/user.php index cc50d2b98e..7dfb375a9b 100644 --- a/phpBB/includes/user.php +++ b/phpBB/includes/user.php @@ -82,13 +82,13 @@ class phpbb_user extends phpbb_session $this->lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); $this->date_format = $this->data['user_dateformat']; - $this->tz = $this->data['user_timezone']; + $user_timezone = $this->data['user_timezone']; } else { $this->lang_name = basename($config['default_lang']); $this->date_format = $config['default_dateformat']; - $this->tz = $config['board_timezone']; + $user_timezone = $config['board_timezone']; /** * If a guest user is surfing, we try to guess his/her language first by obtaining the browser language @@ -127,13 +127,13 @@ class phpbb_user extends phpbb_session */ } - if (is_numeric($this->tz)) + if (is_numeric($user_timezone)) { // Might still be numeric - $this->tz = sprintf('Etc/GMT%+d', $this->tz); + $user_timezone = sprintf('Etc/GMT%+d', $user_timezone); } - $this->tz = new DateTimeZone($this->tz); + $this->tz = new DateTimeZone($user_timezone); // We include common language file here to not load it every time a custom language file is included $lang = &$this->lang; From 797ee16eaf2e8dc562185cd940351bcfd311cf53 Mon Sep 17 00:00:00 2001 From: Fyorl Date: Mon, 16 Jul 2012 17:33:05 +0100 Subject: [PATCH 0261/1142] [ticket/10981] Added goutte via composer composer.phar added and autoloaded before tests PHPBB3-10981 --- composer.phar | Bin 0 -> 533673 bytes phpBB/composer.json | 6 ++ phpBB/composer.lock | 55 ++++++++++++++++++ tests/bootstrap.php | 15 +++++ .../phpbb_functional_test_case.php | 2 - vendor/goutte.phar | Bin 267414 -> 0 bytes 6 files changed, 76 insertions(+), 2 deletions(-) create mode 100755 composer.phar create mode 100644 phpBB/composer.json create mode 100644 phpBB/composer.lock delete mode 100644 vendor/goutte.phar diff --git a/composer.phar b/composer.phar new file mode 100755 index 0000000000000000000000000000000000000000..8b6eddbf21a06da1b6cc983749e5f3a755c674d5 GIT binary patch literal 533673 zcmeFa3t*(lRUW#;gonVyCUyWjgn!3ccWXV8W_E3iwKH1Jjx@WnXGTJr*|j|yiyn1L zYEP?M?rzPjcD?2m;_!$QAm$Zb7YDf9kdP1u0_JjgI1ut61Y;mTNPuv;4k0)YOdw$H z_nk-m^}kvz&8%_mWx)HZ|G(?Lo5D?z9p-_Zt0S(%VYT_jdPsgI0eAC0MAwIh9;)cLvG1W~;y3=ysDc z-G=`8YX4)V*_vlr{(DWY-)tw3^tQL#jczYF^QM^__V=p?txj|1h7@J#3%!0a+-W6S z`<+g*+1oqlx3_nONu%3LI_=F?cL2P)TRk*7Z1=h+lf6!>fp@pst=p{5s$Oom(-^W7 zo!(|+*lH&D#m_f-!(nfiy&8H~*{#`$iIts3e{OEKvBy8^wN3Bz9)8xQ9-3&jw%XlR zy>@>2(#rDM;_Al2;^P}noLjxTbotSZD@&IaYbTS_55D{rr(XWbmw(nPQK8>@^M1SE zN@|2KH#?hwC?nu+Y7b3JY-~Js?&6gVLSMSLxLTijD0z7PxfAa^f6qNXch5cdJRkr2 zsC%A^|9Yhvg2iw7H0W>6-sts)gJHk1hcA2I_fhxU^MB2M^a`K#w=aFu_u$EYpN{{r z@OkUr?9wv+?hbmL*3vR7e#|FnMLwVYi$C)LulS;pikC4=TI-WPT5Iw7>7V{b&+%HH zQBrGZd8s>W^|u19MFALfX?0M?Z*J#_2(1-pD%jbzx_83V6qCp z+D`9wgurV-DUKJPSDyL(7kd4dR@J|{*F;Yu2+JR*5cs_C*hg(RgjyAZrS4$Z=yW0w zU;i-*g3p)i|L1?|AnH{h9&dEoIW#^19%V=P{P@pV{R)RsiN-=}uhnf1A~X&r6a=4- z{PERyI*3YIthE~b&7BCsbwh&B&rQC2(jnYm)seMUXRAQbuQGD*x!?GqKXfRSU0Kx` zA0hIuKS8^~=cPY+``aDF$tpx1YjyTo{n>?fqtn~gA7u1zwCT*}JHFt{Ug~fv*=woW zj@k1wF@ziyK7Zr|KhX2~Q+YfVdbhis9;Ny0II&LR*#-+B7WE;^6`Rnuy- z7qFJ3KMCb0KVG5m`I<)__!kak6e?@Cx>+Ev|MLoj&!7JCcYm}4IW?j`E8voq#^%k& zc5CrT$jQxNuTO-27ov^b;`2LxFZp_h^|BGL9GgpxF2D(xPc*^F=X2in&wtUu%#H!3 z)z65;ziFe*=kW8|LkBf80@Rg$qdVB^^@od3ZnpNgJhE?1)L`HEeDo*Y_Qeiq)R1xg z^Zseqe%+w(`O81?8NcCBMh)4OJdj^AAbkGouXx?V4g?&TU6InZXCK>6f3Q2>YQxUw z@4V@YzS&_+kAQIwqM>~&$An*JQ=8Arzcu@t4ro-5-q7C5V)GwAR*B2!2XA%90`h2k zm<95SpQb?goPG13Ogkn8?ptj^32TFA4`$Ex`;CM2Hz~7;*~Ee7^J?=C{)>YtOo+6a z%+0~UVA$GCUlP_Af3(8l^Hcu%H(YX9ugp_9tv35azddZ+=(N&T8IHbY9L?wO(w}>u zgDmi2T7Q=FHT_A%KE_sQKL7XE{^+|LN@4iZYP0E|(OliLk>m5fed})=IG}=9PHR&f z&SLZ)+rP2U2S3T2f29o`pWpWR%XNqIzz9kioJZQj8~d9#Tf-FESzERE{5L=T_V0IS z1*R-wl>+(x=jq__dH*}=PmK$NA~y8v?|-ra;qx6oH~ZZV z;XQxk&SvL%;#I*KKb(w?~op*G-D4x@lzBCpMUjp zZwAGuuyUl;W>-3mA#JMk6(N16E%tnNPX5T3I6AKyr+ZxW%OuM?t&e;@KJkvAqZZc4 z6c@8p+QUe+5WfYJ;C%kAANa9N$M3Vo!7l}$Vbr%7dVGHBJ72ZuFh@}j%AOdVaI8viJDxgf)Ng=K@`n2K! z^WMLtY{KW+fAzZ;9OfwbCos?Nz}#Or!4JV(! z{p*)&4(U*gte1|_KYWh%jL&a+?mzuRhx3XNY>^{8&0(mA?e0*>Z^G-%c)seBJ|&o^ zqo#Sbi-Y0m(`iRPVDpO4_n!U9mpg)oc67BR&JOSBR~YH}?0w~z{HVhjC87ndN8mBB zVN9`lxhYnBEW52NBMl~=X~1( z4r+STh^m^OLgYxk&_k3xNJj-2Rd&Bz*qlJ+;?6ZUz0w!CX`u?KOj5SN|lGZ!l8v`GK?F@%s*?5*dRc z$fRI?|Ajhud~S4~dar{iER#88R^foMS1GVpL02Ou@cG*x{jp~p*h}-n=fEztcA?|i zFZSv=FIEVA{^XDS_CIk51>cfGxY&bUB~s;ie^IOQ`EyCFT5IsC_RKKg^KHiRdFt%P z2i1DAst2555&mBQ1%VXc^T{u{{;Q6EK}b3hkGJ{**l)A;OvUzpw1MIC4?pVSvO~JB z3X^l4c4H8sa@+JeKELY+-+Q{x!CUBxt-z5k>d_MB2-}F5Wt{^i?z^(3gT5By? zCjX8Jd_G@u@QFXk?hsGuvJlQb1cLy2Dy~n`v`JF#|?H@Xhg~hj|v*JdAYs~yd&o}AK=U0B{ zm*3`~UOfhe%UI`RpJw)Ix2?YB@QS2dvRTFFh5z{@L3uoTG}z(?&j8-B{`2|4wf9|j zq?e8a_)?>PQ{-nb+A@v&lwr^3lULu+@J8m3)QG{(K#tAjcdh^4{SNtPbkRwb0sNab zqxgK!xBvUU?SNlfmzwZzKtHXHt7Kg8$TnV#8;9;fKRQbtDQsihbK1Li-h?H=keJ|L?aP zsZY&w)&<$XIAEO@!Trct!RIHP_}x$OI!V6H)nU66xqVr8&1M6iU-jBg4nB|jO6#KA zo2>zso7U#OvF@AAsmA9Qf60%3r#Jr6QUI&a?uV_^KSUIM`uU1JpWpD#um2AYp}-V^ z{@SqLYV1Nm8@8StMnpJogyZuiuOH4iloO?REcW}o{$toi?x>~k0ux$%zU$Rr^nZKp z0!t|ld(9^=YCytLv(=@Is9-+Rc!AINeCE5uJ{txmtIjEyTQVuG*%0vg`(Lp6cf9RF zCHFOvXkTrbE}!4^-M<-J&xLWLi8#yu!2jLe+lRAHMb46;%IAyUKl>qX^{?bn4FEW5 zZ2G(@1$;jLH&T`?@xNPJe16M|zV`q0iUljqD(XY3i{khB#vpwD@VEcV z-#VSj*EY?)XA`ut$Ycne%OC z$nyE{z3;1DJYD1I&tC5J_QEfc@rA~Ae17+H{`iI7jlx0|YRz714I6vvkO?md>f20k z@cH-NxfN`+!df0`$(B&~MHuh1sl(^*e$F#N)h$fhP;1tLKf()w`4#IGpYwC4-{ZX+ z0p?=+M!(UAEhRw|O5lHN-giFF*FQDvv7hH=p`fUcW%%b68k9@NUie9~nidaj$~m^Mn8B@t-*y%=u1ZFu2s%%YgZO z6C-@y^X~urZUx}Sxe%aP<1j2cpP1}H&9$=pDHfC7o3qe(~51*g*qwfgD zNx@}?=&zl26AiYzd;7zg)|0J!Frci;=R5w!w+8)#z7ELJX0JaxIN06lbq{9uR2i6E zv){5jbs=v#$EYUEuI%jDpJajmV7$oZZ@uAH|AF^@@i?GW1)*p5Lc4352)_tPLfrZM z%7g#5>kVMIj)A-Ofr!swwh%6#Kk~{i{}G3K{tn-B``f#y5h$GIR3(}ydt*tltuE-Ctk#QGhZ^nBj`&UbyZ6Km-X8Zug?jXY(I@cHD`p9^Z| z>yFb1qMQ-H%nmK@4G063+Y%g$@-%XzqChR zYilu|Km1ic^{w8c*WN*oO1kn7Y*z63H-BOJ+r61%O+D*MhHxC6AtVz=htK*yPqp!5 z^~KH(=p!>MzhF$j=hvM7_23}BlV*a}qV2Ia-((}h=kI;!g~7I1JkH2udSkoH?=|I{ z&#!sotA;)%$6_yQAb-^2zu1uQdHM$*4S|oxn(&!UkVLOCg@Dg5{lo7F&|g&~5o8;W zVHB+gHF)92cf0otJjLW9KEL$0=f2m;^2(zFeYDfNK|G&pRN(VRZ~T+*a*$V!4$^m% z7~449S#5N;>HmlN>mS%&yl4A&uJ&H!?OnWs_SSG3f)0YXonNwk@tHjK&7*A6!|3l| zWkUrk$`CqJv12bWmgVyoHXi?R?^uZzKX!v_?WbB5beuM+#pfS==4XF~H}{4+pd)PT z&VIACfWwSDGjmYvzKA-4(+G9@3J7Jgw92Kl! zm|1I%&!2zSi$BhryVK5HIoK=d+;wY>&ky~?QJgto5nN8WlL?qo1;Y4~CTaEw|Bd&dX{>hua>6;uL zLIcKy$8E|q$npzcssQ4zue3>K0j;e;};wj1H8uUED?iE zoAGmgx+xKS{_(r#{-uMBRMR&>mbJT#V!zS5EyT3_0ih>*ye8WPa`;=L-3M91KKU_9 z2R?Vc_uswQ8(BzkKUO0Y3}oY1^R|n}NvZ=Ku4C|LHA6tHH5GftH94d&W?hG?MZ8 zob8|eFWy{aso!C9?9>a*5y$8Jr~lfg-n~{j9nj7E0guDmK*_-8FP-`J5ZQXC47}Lg z-(@fUn$e2S$@=aOdN1yjz0`@t7QV*Vi_drc-dErB78nU=;$!U5un-K5{fLiae%AB9 zI_yLK#T0PEwqc3KiwqAwzy2RKt~nmHwD_jylkj>Q)NT-cfPFxMc()OY&*y(eJ-C4G z8wFyMTerx_K>$=>IhuU_=C}RIS2+&c(llBG9CEv^=iy*vRX)GyFMsoY^r~=l-@`;G zUhks}M?Qc16MH*e4pZ-*i%aJhFRv{=XcjJ?pYZt)e1aFz94Zs(zpeeQFe37mi1NI& zwfH19NCx%EEi8sp6HiaHw~{*l-e|=irjn((1;>Ol@{%X4c5!BSI?HrlyjYzBCyd1fbyma}bDQvyD_JukY*33gZ)cclI|$kT@osiQuj z^_=VJuv+R-Sl0*KB0x?5$z%c2mdhn+@4|SZAAgWU8CSq~7@;9X2sv>k+w}K86u;@o zj#TE=j-;fJrlP9giJe}*{gecF48V#F9!f{rniv}9@9+`=0gxjh;5o6ke*=Lb$nj${ zWMc!yGZv%$&0$^W7$|56o=kEyN+uP}lSv>$G8xe1XHdX@Vl869P z+9e+&?E`9T^dU?e!}^K!Cr^FW2}6DAWU|%h3|go|+!a`%+o1?okq$bh-JVWL@Ck$k zNcKjn%qOR;Cvg)IR<#79BV}WhvKU3hLnnw(RKr#`AAGi%=~PWQvq&{|%2BW4-NxW% zeNqCyp&SBR7?3<56b*lN>V~;5Q>0#NNJ zbUFf3S*Uk};JI$o6FX(_yQSHldVLU(@e8|qSX<627v3pNGi_F5Uxq^3^eC#MfyWS8 zcdk3Q-Rh^^(wR?gH=jbiMs?(grOOM;PpoY`zPP%!w0wEvk*iA=7pOdeDPi2B*Fn=- zU(ZF*F`Ju;1I_$aOU|A>n+*1EU=q|fHZCB&+s4MpNaPKY zcB9$u*QcP!6`?aRUY%&5=UNvXH?-`9%!ADIiI~)7!X&7<&vyF|O1t#|n7#vTw6!U~ zDU31@WCB{xOwL*BB+EX%HJD@pp-9=1b_Auo(p!tO5h{Ht%Mk7Zv`)kyllunA^k!0P zZSM4v<=57d2j-L6X6x20<@bq)CXm6Rg(H%-Ow?;P8iO6krP@>axT%j#Jf@Py0Ag^5 zSQDQbp>c=`WEkuroM5Sz-}+%pAXTpA zfL$NEL(eiA$-@xj>{J0maF^=yHP}6U+C-Ql^P($ZPB|xrcaT+ILaHqpAHu^ zcCD;*6pQG(`H@98f}O=Vv?Pc>GrkoFPMw%gON(I^OUu<}77C7SFo-0m;?4G;M4LaP zMr0(UX35Ap1otE4k z7ZQm8EkP67=3FiE@enCMZIS9Fo2+5@tkkz4T(9VNTFmZ>hmd0Uara6wQKoa90Y>ZoQ`XyA|#eJ>R53k!j z8;(2&U>KbS96?DoPUFB=YuYwk8^o9}J<8h3eb@;Y`M5#k1bsY$1ty zM00|4gVCEI*r=vBQ;IlK2}@dVWl2lk6T%zr^n14xxEYi6`~jo-MD2WUztc>*J&+n^ z2}sx|@``Q)0NIntf$vtdyRt)3!-fOA_;lRS=wMQ^md z=BHD%rLn5yB>z}-bXVlCMwVr_tb7a4r}8?3ZR9`fd5%C#1F@dZiIFQ~nE=+I4l)gu zKQ&^>)&{dl&x;&1elspkeC4sk<7V?>3K{odOvU*h5(*M zMRIa&O1~|%29jj~Sm9m_mBF0IRxH`-kyQ~Ub2n7vTg$BCg32JbS8y` zKr%VYp&LE*2||*A_aU#th!;rk>t#O7&8;qe_SL1;#RWJgj9dv7_5=;y;GH3QkiA)( zI#~ubU{eIVymAHp-*XoMID{7_w~_Ar08mZUU`j&_*2fNmO7k1Gn!VKfi`waJuSMEEvX!vW`|i_|T@Y zu|Ep0c>LO%H^DT`_7=K??Q3`j-4mBEJb;ww^=>9;TLTW1C(1E^gLqW4Mvfp4j>;S16fAhkNZJvkT$o}IEO@EA77>u1iKSzNv{aRm%VfLCr?D(+(h=h{65528IqR{oUCg}e+R~}8zJvpHv z9%5k>*9!=z((i!{DZWZexX8|acLA5>mGUwi&hjrQSw%cTs3;M0@i8{?-t(NID{gaH z6}eK=Y3fTWG;m!9Can|c3&nd)mJ7TU_Z_NQOAD=Awur{tyD7F;A%!CkeRCJT+}05# zz;C#)gf?~R<7P+3EsG7(+!ZxrMP{91R5v#cYycR=)JxESDc^H*I@{07dpA#0GdF{! zG@t@t6^jUwhEAc1Sw=Um=b$fWcmONOl^cj*V<$i&#YX})g2{SqMJsBtI5F^J89x6q0ALJS7Oi zH-SDCVAMF*ZGXFhV6Esf4pY%;kP@;76m4*!w+QOU5DQ!_w8|RvOI`7r`(zCP?)4M) zC6;MuNHxcqxOnYwxCFhl>?nLUGU_(I%jvG3W%)qnalM}&C>Td8C{$)lZpky>P0yJg! zFxiFGaXLjJGFngWb+E|QPt0=bHq*5S(5H-ufB@aRjfgYgg3!^$^H-KvU%#=oxN>gw z9R9>x@yT!O_4Y_4z*oXzm)_uV@~2e3cItYx7ax6)P4`fa=+ZG^eMJNykJf;<4XDWR z$5maflb=G({FmApW7{%x)Z~X`?|Cd*O&7Z*T}`<7*U-mIvtsd(9yP=U;9+Q}y)`$d zIw3yr7_#Orc`aX+L78HWiBR*ZrCx!o*h4T4V}f@jJv_RYa<)y=OkaZNMo6kEw(77P z>AlEO4KpK#GCdDftG(qJ-vX=Zd?>Qmd07fE^Gzjfca_z`lqIVaIAwDO-y=E#I(9GM zI=&U%Jh{5Oe8oeP&emo!9nTFS>j%$raT&Fx*;jrUSsxSJhN)|9So+jIEg)?WTYdEe zWR`D?Ir&UkF1QMAU7|?xmLeU4B@iF3e!o)RM5$7d(}Tp}x1~8zZK1bCoq&TfX(gu3 zL@U=+j^`&F%dZqP;N!ZMxMiR_3-J|{cg4xm;GLGKKPK|+;Ic|w1smD>iIffQcCQp$ z!&Ua7)HJ@Ic(!Z@3+y(j-|4!Eu1Ki{2xgy7R?Ii0sw%B}vD6R)=qR4S+Qj$sp%yj> zQl}Oi#k>I!k{*z_p76rioDrBao>3hdK}QluLbIHiSZlSajywo9xDX?A@$JkRo>>v3 zl!9RJKo!xb2=nA%zRMlBi*yx z*T#kI<=vuyYpx13F4x`~1FQ@oo!Nc34jpVfro_9--$}*@j?E_04R;y`1GvD3lASSz zVoOGloG0OvoapdOTtcr|pj6`)s?x6pALFyNJkWZHZoM;oVI_K{H*K2JqRnGcqIf$? z2x&7~Hq9X2&xGFSVzn-w6`UHtJL6nxBWfUCh>YB`<5#13(@%@0giI zefluUn#xeXO>`P6B`^b;1{A9gpo+Fw(&>>s49chwC6#0DD^gCy5hb?V#%>E^2O_g-zvHrk*f6H5vdwEI*r#0-Qhzw4$lGk1)!__V|v?Xwq(j9+f*aE-%oah{8 zaje|y&^QmGFq%q-RoFL$J(&uXl3vZ!Bs@k&*A5Gs43N_j%=T^~B*xbIKLYO<2-1Aq zq{o%QtrQB5K1jS16by{9GN&|ucun|LR=qO#%K`BK(5)$g6B%gu$`o7M-^O`IoZVJ| zpXYp>QB<>HVCBqmpbHYtB0ZJj!;u51M^UsMcHj_Vk8x~Z_K5h(w=`)jpd`Ih!7>pS zmey0J0_3w;2Rc5_9FaosSlq%dFvS z_+H3ra|PohWVInAC3Q$6Sv)H1jRt27eXN+sVK$V~iv?|)%U~5p z!GS6J=r~|Pa-FhHIxm$|y<9xVh=5~n4&Xxxyyo=IuyT%2c`h!lxk*y${c{gCPUg<= zL|MQe2YzbTN~?rXoT*)xvt>1$JxnFcT*`ZZ$-~3L9uE5%!onZ1SqLrV#CjiRw?*$L zQ5IY<>B$4ytq?l-t>Z{(UgAm+OkFO1vN9}7{8jYcS4UB8+6*v?K86wJ*OS;Y!-APF zBhrMvL_NV-x)k2BFdi9i-=&s{nO&t=kuF(BWhr#!J^h{nH}Mx{0&uY~CSpF8#8c;1 zBm^`Tc;xy{9`{Z~Gg5S($({E04wgdW7ED@hMa9cpkqo|$3l?cV4Kxi39>=twKEcu* zu3U>ysf7x8ly`_u50b7a^{=>;qs1Z&Cp_}e1s9b@_s)7WifdPT=Pin+d38aje z7y%0UJ`cMNvY;eCgqj5z{9bFNvGhOeM;FyTh;&2a4QI$w#WFj|P(-O$-DNHlj$;M! zuE;*!h`vH)YIO%G7$sZQ3d(f%qE=A5u4*uJvpdC2q584l+O_)z*HZz19|-LV2TlZo z#F+-O8;&KPu$&IFOde9kdfK+gacxoSysj;)~WDx)=3mn}irG%-zO0BLm?BJo_$HEFz}>RpLy zCo5!-#~)Fvq%cUChGcq&xj>6ENtQe-6eO7n-HgC2*`ZerLo^DH`pH8cej>%>E|w84bV|4u9&I!-c{J%Rmrn+Qinw?X14w`6i zyuIzHf*W7Q-3}+SYUs&Txpe)!9+SH(8_fFxG2JeXf={%ufu28z(@JvRmU|2E>%k^< zyAT@&*mh8O8s83GMs}-aurq^mBZ#3r)~O>Z#X?P|*p?iw(IBgjxHCV^{=qb&xj-x0@S_ePEsOI; zhp4y58^^JZ43ZoBLp}S1a67P;oF2fC9>9=8bWNS>NqZC03;Bw=+$Kg^sA))5<}QTG zs*F-_?2m!YqCqp0S`S4batF1iZId8uQkS>W?IFiDZyhG59M=1_o9#XAyY>-%=OG)8 zy#YbX2%3;{Ir*J6W#Q^wd0p9*!F&OtM$egneF7XqmpwV37rI>spn3=l-zG}->%gDz z&kuNR44X31Btv>v1ZCdB2Lxdo`E*mCkm4CT;@D6ISvm32Y475s4H4CENnjFA{6?~c z2vl2&pos8vH-dx_Rs~VOO7^Kfn3~|X+YN+vp`O9QvNG7;+?lIB$7BWl{E|9mPGeWI zH_O~u5?Y&Fo0*vjCzqbBXoawIP30_X#6tRtRFi-LJ>wj{=|OAvWuRJElYEmG&PhGa zP>kucvz`3|9J!Bum>&D!Gu%T$9=BYhk+0N?+Ps9yToxwUF2*iM;Hb+queHz$v7_!U z^#(Y#y%^A4)9Huw#zolC?DQH}Wzo^&CW@fGd1414TV$lT`!4mvDO7A#@z_&v4;trN zFe_Z`r(isvR-^gISYI7_;3?^}uLbsvk&Tex*dCoND%NcbPI<3w2wq zCZuXcE~S7gH*0{UB#yx9m)3avM`Wky&3pJB2iU%gE@@|B0k89>W zXy4%gVlL`>oD>~WW^J$eq;1SBHF02|z13!}D*LB~vo+VMUK>%o#9yk%PAk)M$Vp0A z!Ray<+0Wi4JSu{55+>rU{ct;-6Gnm(cI{r_km04JzKLMwufDTzTSxzn3H`-sVy8qSwx} z`u$#iUUpnqM|@Vab|t0`o2T}jfNk$t)JS@!kzpS0%|?@!uiYh5>KQ9<#8O*1fa<1@ zPMSDooY0e!fNZd{Sp0Ik(eJ`K&lHCbce2xXQn(NFvlNk>qFoUU)^1XLD*^ z09CYiFr-V5H)l*82}lL6w72L7!dMX;^l;Eg4u9EiXC%ELs_qTV&0ReAx{bB-k1bw0 z2a?A~GqebA6kM)4i^?lBsO6r40Xk#~YC@KNixVLTmSnQUu}T9vTH=;edRow$C9-EZ z*@$oo${=S3;2M4PKFll388FyM1eUp)4U~GXWjN{-M)Q$ zW`Gq#F1E38;>G24@C59`tAkpxVg^^5F)K@5({SGIT8iou9U!%wWz4ZAWT= z!!BH(=zU~16L@eaZjlcKsv(4U6FFFV{aJ)#&l zKz(E^NR|6g7!;}M8#1-0>krS>uQjHhx;|AGI8$%2r#|%46rtDF*H5pnKRCU<{=oYB z%S)TX49qqracQ_CFbx1I{u%;B*t2=39fQndFHUd`c23jPawhQovG!h9E-n7|7#T5HlF}0-^CL>Yn8du`5ViawFIiLEtJERK)N+`MrS-70ZTh zb8&knEKOh!;cGK!Bz7(U_J`SLvKe;7>(HV)u3#Ub z8THN`R$-*~QZ2LvxwmeBo??M*j@;V}``xMDJK^U^Gz7>Gmq3R52TAWG@j>uirw37l zrAiE!LkvIl?Z6XlPWw0^;~L_=nZd6OmDKl1%iRdYiInX9^$WnF2G*#EMT4CZsNmrZE@ zsk+`|TZVaKB>p4VULj<%ETq<#H_A#eWl{s$UFqre5SIo8E1@qV0jJ1N{smn(s=lI( zSB6!#GS`Xl4=qa~?vT0;0VNS4!j@5BwsdF2)r2l9bE%xU`@jXF+@avpibO-`vGGkR zU+|vsN8IcN2*1*A1Z5Qt95QUtheXWI)o}dblBTez>(SfrUJj0;J)KS&gna3NH+ z%2zPd5nd(4B{rmkOfMM=9yAZ3P`uL$x44ksLMdY&pgV%e&aWK{HsMJPqmN<7n| zdR|NhQ6mo}gfMgj_^iN(H9XLds)2Zrm3y}$PGFdg{QKzV!X%Jzdz~gZYp=}R|7RQpOuB%!nN_h(4fw+Y@T1}Xx zk~~4^9g{dVWC)jdI)7FT4X4ENazZNmQBE%Z=!1UrP4NFUHX4G&JUCk3^_A#As^^=? z>|m^@RIJFe;|q*`UO(4qheNHoO^lGohpU|`x@)aQe{%;w4%KtRf_R8BIuMAd8X4_D zXk%gsJO8BR#!xqL+7KsXTsd_6U{nBJq=#x{Q3wS?qA@b3iI&1@DWuVELG?kkRFOsk z;Wkp2bm8TyOogm9ajoO-R=8I0VL@rPKI`Pp2_yb2Ou6`ZTZCZ2Dsza)l$K z64OEYqzTK5t?oA5hiB>SNEa>PlS4F3(V|Q`X-tKtjEu!9eJh2--&QBqfke54xD1Ej zBV4SjqR3LLacYUWIyu1o-(ni*EEGSqbt{}@z$_2|#TE!dcG;49q4=SKDE)L9;;&FY z!ZHOVvzwv}bgRw`sw$k^Tub0n6VQ9k@K9_A{~L<0>hQ{}iH#7w4Lfv&kZ{v&00q$F zfl6MMhZSq8)+J|}g61aLCX&j8m5Tiz`)s>zDvcm{1UN_Q=u1TM=j_!}pON;n?9idxY3(F=UWedw5~`bGa%^7EebOjEH1L9eXL z6RjtIszbI+2tY1yV=pTlFc#VzkvyPkDy|&MwG0z93IDUH<31qXauNQRFl31)!bIInlRM@`{?O?6s`6jn6Q*cLR( znx~~;-DTw5it4ZRjggwVi9IJz$#MN*{1c|o?INuW!apeUET;}ak_NcH6r;%3%GPqC*VB4m>w-0I4^C)9oENxR6z%nc45^mSC zchI1YzSV5Ir==_9xhBrmSb=cYiEP0ln5`ciUwU0j=EwrLX*UHqh9}VV;iLEheft=4 z(1;U|)KDA*?&&HER52uSI{aiJ;a)uNccamtJ3Vu1<`fS&+KaQ{#R+?p$eR;)a0C@( zbSg4^BNrMYD)+3*51k^u4x1W&WbW{Drx%`;tN+S&%PH&({UC-Kcf=^qqMouoqN;nLZejY706AlaNF@#$_<94}iylaY(jt2Kij}rnNm-tgZeo z4k+0?mCtqx6U)9XQ23)Aj*hxm&m7AX%|@d=V5+e@X$9ZAmZPIgh!{;V$$V(Psf1CV ziZKDC5H%9U+RCFLAm&|09yy|v6PKbe=i3NwqBxn1 z*ua50S%P4A@-RReWtiK29L24Nis(5uKnseBXv5K_YlsNz*jNM0ggv zBR)zQM#thProppxp!)HTkYmzn)s8&$91lGx4sJ7Nmhat^oN+F0+E;R=?Z$J=EeeZa z3b(V&NG}f^=q4XTy28jqo*^>^jY05^d9KvJkU(kEi6W^KX-Z@z1;9y^ayuTmJ>6)I$YIafdeZw?LIlo9OR( zCxIAM2Gx$Bl%5_#t{bG+*c=}F5>CbT2ri~|T{S>r>buoCy%t+vkYz`Ia2Or|YsBvW z$D%Xif_|}ezBE`T`X@2Ho2eHU1pMrM#P!3cQlEUY{RJvpl6SI=Brcg8wD$0$HV5@I zj9n{1&$gx~uDDw=CixdYfq)^12U2AZpb+@r^A-~ASlwxuu zN1X2U+Uy(a53ih|k7fSabh18ka{cyt?N>kVYp=g-DmlYXtzB#(%+I{+;i>t_*?lsu z0>d8yLbL`|fNh_|6(b%Qh1}V=1&5a0gIUvPUs^bKWpQKi(z&IJf}Kgo!Zww984Zr^ zesFh58`Kc~OdEE(3SO_BJ_3umWWco`M`K(I4W6REz6T%Xi-tY8wTYEV1GX~tptY{8sfuN>qjDqL^ z@#W$2QDoGLZni~^h7XfJ#5y5|Aw~@2opc2A3a1_90(%-w&rZjx{2rbNq#be^V+M;m zPOsD)HRUF%uFM&+r^8ujHqNu?BssEo0EbUBkrEUs6%ZOE04X2u5aVZ^?}NLh2G-R5O`6;pw~mybp7$fi))QJzB%V7}+5^$dcuMw` zq@IQ8mfuS7#h0g9#Wyig!+HitqB`K-VEUb4Q4Ma2o317%p|C$1;$Bho)UOz4K2xZ*f+Tp0aA zX&?DL!9yHM``2M5Fpo{=H7hup@vv$SfkR~v)k@-~RN3ao9hk^e z&Ox!%WBRBo>zaJxh*EP>r#Cd`4A7z*Hk2dR6!R|56 z;d72Jrhv!+Zi2wn@acqLX-uOV+h=cJuk@j(?{DuUvN&>!y=2=+k>YW991;}K0SD1-+?kcmq!TmTc@#E^CJ;4H zlCm9q#>*mk>$(;x>sulEr1&S?M7NtJolGQSl&{tBpPl_1GjMA3eUWhR#qTm9<+KOO zfdA4%^LG7P*wep?&Hc;gE-mJPmK3Ol3emV>y7~q&v?R=iVDW1?Bsi)tlHMzMQn1AR z5Ff0Ui013LZW9~o?IzP{I7dR>dx?zbcGXkB)X%6?nV(ftl*cm#1+Z5Xeg{(v94E;! zH0Qqi<-`*s($b2g4jaNxC1Gs(KK({!n>aQ7s*RcH>o1#)e@s5pCI$+Ojf`jUamUMS z47CsZ>I8VC!TjM!JLH9>0p4lcLIMcvK2j`89jGh}{=r%EW-`FCy@MAxhnyZLWrt2` zjtb0_xg^Fr(+Rg1-FGX!At@*>Y%(C!A~PAB?3hJxbp=zaeELKMu=*flxOq17HGTXU z2XYu&L>3!IAZh|fa!c`!isWk)Wr6B<<=rvePvilY&3N8(I?a&sn4nCfQTS$F3Kb@y zmEanL@<`!7%-m&fcDQCynb6^6(c-~I$Qj#6@C4MDZ;ky9Ho2j4@*Fr%o2M$Y+%hba zj`o%WLIVxBP&p2bV#+I24Ip=Z z6V(1ogrG0GrVv1Ums=j?vr)W=Vpb#aq-DzMh-?Es-J;QzK|iYT0p~Ii?biWVo4+EEOdgmKff zf+86866#>|i!>wJ+?fW#2++*XpIVcxT#%}cl`0z9ekC)o+w~lI3W{u}S3;*I(I~QT z1jf;JVb6u|F7dAxy12`I4$cc&dNM`@Lslr-q;ziG#8u^pIkrj)VT7^}bd?Ko#ku-A(tTf_@j*$?hlyjx zgk7#!7cX`UD_n7$)4@cSs6Jbf+ysoxk5o>Tt{#ijT7!lB5^g)eV|S8KwGA+y!M~nd zvz_AWCpb@-HVRpV%t%V)2n(VPti&c2yW*@O+Fj z;;3ZTRefpa#H51aV1`B-ETRgLDK#Y~kX6vBQVD-GQkqra%IVhB?HpB%P0*@S@nK)p zY@rN^Uc~eeIS#H;i8;=4tCwUL0m)0LSTOdP0!m1q(_zli6A2|s)cDEd0gjZvN>7*) zR6I5YOy-sikGasi=M1>aYalL%Di1^%kQs3a@m@(}94;;JkQbV*!-=Bt68&1OrS! zaZ(~5cOr3(fb|HLV+^vE)f=mE_=gb!9Ewfk#?<;N8s<_D>j+j2Ea&tLPj}EkxQ80Z zmA2FBbZ{i8PR9m7>*c9{Vlje%$vzS8^n81;w!g_sgtqo^U97NPt{-SSFmNiVH*hTy zGS3RlLGiOn?Ojq?;DU_Ha+{PW>n0RWCM&CpkHUj`Wn+1DVR04ROQ(dDk(fXEy9}9Y zDqkVwg%_pVHIwizBy#x29O~wb4`19jh?)Gq68;>qV}q`n*APmd{w0BGvifm@%ENH- zyF7+?x?V!A4m_1U+~uHQfP@Z?+J1(D%+^FGeA@wP@fETKrh z6^#nt;2@N!TWmVsS(3n<#f_y$FE6hyZd_h`V&me{<;AtkU}#+mCjP~dU@y|)r%@&- zqSI0$ng|~En9A+6XC)LJu|rfv(GW2T0-ZRay80*r$K|CQxHF3Hj5ghDZs3~V6LJjE zQyx%hX56bJ0hm6m6UARr%V!O88agd4WFtt^7C1i)7M%SyYPfI_)iD69KTb^Nf?b~@ zp=IISc3zs{JS6< z{xxpUSVA^j4WxT~bCBXGw~n@Sy-VK)@Q|}9Yv3X*2GxPBCBxe!{-lJ;yd#lA+xd9A z4Gu1F*=K-l+6NeyxM3^TYv|Jo9=t>`kJ%AGLnO;tS-ck0T_ua&>_r@1kq7_+EV$mq z?V?{&3R{g7d$h_%tbE$ENG;Gb-!*Wq9Z{3*c)ifOPV7vI)bx^ZY~HFs%LDMkj`ci; z1j7O>%3W~66w&2crWhQ>8^j?SB8|B+`mSc}Q8hR1q+r45rpzu}`P^}oV^mCW38$bc zcCbTSD7Mdwr$z)k2qvpu@EKU^_B|2?W@mDops7^#u z1s4ep+q>zp@OtgM1j8_2KPDvXQ3SOeU|)fMsDv5rFb}B24xy8XkG0iVR;qNVN|%}7 z^@yfuaSG*zKo`Eu;#UgM7^Dau_$O)i6})7^Ph( z5P@Y{LcSp=_r0>kNOX&|se+C{^Rs+$Ib6$V!sByybd1UUeutRa%#vjYX}yP6`!GmV zx4)NVaw4Hx(V!bVlSZ9hDpxAlSfx4<%OxbY#N}Q=<}W@8lw>aonh|EbL>zL-A?jvv zwuaRC&^N_BeODJ+17EubO0ky2FOrnRdlsn+GE(em0E{1` zB}qYI6)oWKH8-kNc|Zg!C=hTyA_mf~X&E3?NAAXfO6dS8^yLB$jviwqYZIuJ#zcY@ zcz{)IcwrO|!rE{&6(K)x1;gTQ$^J-}KJQ!Y;el(Lni6P#w~SBMb{eP8OUj`AU1!+I zPyz03xD0kSh+n-nKv}v-9NQ_)b>pSSC5G(ucqJxEz{Nqh=!#nqc3^`IU=I-gZ6R>2kmj)O( z=dL`qbos*a#v|v}78$D<)-zqag9jm~cBzC?G3O?(oLl8S*#<8qZZ+#Q-t#pWbaYet zlx%YO9xdh$+-%HpHNftAZq61p)z?G9$i^$eSmj?ZoK$3O*naR!Be}D zNFyeMoIYq?RoM@pqiF3Gwu=V|9@Nkv(D_fDJa`u=V}AZO`i2>-e z*~5;Cdu?&u*eotGgYb|7#1Tgy)Xk(uc$GYA_U_zdh{zV%zNJ(vd|&m%{9Mw!yBU#} zCmP+E?B6Kj3~)W$=FXu6%8{lxk4G6g?6t01HrsJp#)hfJ0YYNF;r<|*o>r&qG>1hY(3ph7rGQdBQbZ(ro2`IunjHg&b>UlT z!>!&-Hg_7`?H1(A3_Qc|4bRwx8s3~r0M4uMuS5iUza^Om<_S8;CZP8Oj?{Slh$At{ z_)9F3eav?L#@3b1{dxpSi7cjv>WV=^p;}7xvL?|yyN-tg;Z77hZM;TB5=W%zErn}0$ z5qZf%yk0Fz#Sj~~QYLtT5x&87DZ1e{8i7i+y|%J=ZZ*Ugh*oML1Cl5+!F742yaQ!u zjm8hM0?N*UvEKzQiSmN-C@O{T))&<|Z93%pg-K3AUx6~=`k>Iy$ebl7<9MKeq*T=x zH`?$%gJp4gJ|EL(;IWXea7NQ46s;3_4F0%*y;2^gGK-3rJRnNgdL~I17aGHc$6B)Q z?4uRqL<+cP+wqk|PW=GBm|0<*bY@|`18)plzG`jOR?3+tccJDSF2Vfpd_1Bv$rA26 zjHAjTprXf^<4x`5^T=v>PQr;uKpVvy`|VCM`cgzyr-D&F9e$dl!WYVu5eO8`cQ*`s z1+bLkN?ejzVC2+Nlj+ILmKUD?QSCt*D4 z^r!-^MgcA1|A2t?7%e1drD}oH#0-u#xfJGS%1kO}dB(WR0|r-07t0W&RgWQs(hJ&0 zbj>hgQwSs-!NR|X`;0M!XLFwat))C*h?jIPUz&drZi?K6*e4} zAY3R=0;6lNXuP5pc)TdKeHzCLtIrg$dmOE(SMOrs5qsMd3!iqjU^%#2#NAk@I85l{-!_v*A5wKqNQMU?SM3h*q`t)AO8$U}pQBVp>u( zv}L%Xnvxg(?FcDp3+BkQ87G@JktoFg%Pbt=dk?LchdhVOO>>wypl_sM&@>IH@fvNH^5FR?}70SnV>ROJ#8l?D`}m zCQ>VO2Xio4tP~9uW2VO5Uguy{=LC)UV7O=fNNgk2tP2wS` zaGH8Z&-SGv!52g~W)%~Wy9F7T!TF_=Ffd>Nkmt*=&L~H50a>-87a}q&h_<;bM2x<| zP}UUH;OC~GJxF+NW^|G1d0ag-UNIFch}}))$lhx60k zYcW6b3WkL%6t_7Af$ZNKqCG}Rgnob?!C7KTME0F(9+apJIgJ*{LG@1E5u%puCfj6? zwjjwwLU+eA6E|=e>?Vni6~DmbqourRf#pL@;e_f}#Ij;_`^uCQ z#z<`DI_3%@+m#Yph4hFju1_A#*;}nAXaz2jxm-$NPB|qp|6*(Caeu1c{hO?KncQi9(=6ItFU` zg^!QYT?ge9Ouq#Cm8^E?cuOg#2y3K6oaK;0cSa38$md9deC+1E?e2#1vwYDJ!Qr8; zxj9vjs@!{OKV>nC29hpgP5+S=;qP zrgeC&ov4YuC53Iml4DeYjI(m)gi21QiTDCURRt2be;9>|$PZEK^SnQ{P;qIYwCCLW zQX(Yk55YS4@)dT8Yr-y z{yBD}XuO5y)h{)=@KD6NX9z;$&UKjWcJ#=#TA_GhGc0Tj#v42E%jv)TUQ%X{tgN~5 zgFSP#Gsr{ks4_ySpeR%NM88G^bQX8^aPWC0A`OCSqF=JxtF6Va*V< zG(u}WM#y?LIXu{dE}FgxgANTg9A#1@P!i43W$JlFSea5-@k`6Yu;M52z@~4jaRjRQ zp@g?NMM}{;1Phf-135-sg;3s&UmdD?wR^LR{)f;X#51!?Og$}qli8V}`Sg6+RVP3+ z4l*1gdwHSPUs4A`%Tv-+hi3^f%+!g_8wp>iM^zRL0~nQ{4@s9A2^7Y94^}DyQe`S| z0O2Z8a|08cNPG`>)D;lP!UVXFAZwbciCog|T2m@Ta*bgPjds2($2N^Yz*rjoh(sN< zTD!QT9_JS1G87{Tc1aMvflGamI!faZ)U+*9Oan$!9;l>0NgLsLvA5mcOw$?o*5d>c zwrZdxSP#)kC%9YGkWB%QIOay_1SS@L_Fjh|^8q&tWq#8g00B8Y zAHRhNQywKl<)={0YADEAQ&ucxew$zSI&U$6W}$VfE?IQ$&5C#Nz@=?#q8)ES=3tm$ z5Nt~-9^%;ya|q%7lt#f(AN6G{2-&oOP~ zHfDX2#24|B1@ewUw!{3AWyGhXWJ4$u^QC>V^HJjK2;UB%gLM;CR~E zP=G#%aeyR5;q@vZiF5^BiqlS-?7nSULtN@7;wiGcHUv zJ*#yM(jrW&{Mt0eQ3ItA2Z6rv6#Kg%4gBDQiQYWPfcCRx3!0dbgEo>vZy_iJ!5%<& z59r{j_NT6oJy`C;v&hJ(`5KSDo`)y2&5Thn$*&hU~FN{Gj{(-940 z{j44L$R{)k`Xc0kE{bycgH(by1Jd#E3ajbC238}(l-3i`U<+XW+(W0=$YfR;s3EazS&Ifdtg%j#K-+TF{b>?h_W>M2Yk7{o>BwE4GhVR zQW|crTgc62$-|U)N)!UbGEu0lO?YuVai#V^lGHFS4$gVx+Qw)s_WQlQwxotRCLI6e zCEwjW*ODJ@*DS_mk3#voz#VdKhwOpqRdC2*O_VSQ*}<6sS7&z8Ux77@DbiC#|0}Y_ zT|3v~-i%sIIT)qWX*$~BiWKT2qasq{%*};LZj$<{4t-4=*xY;1=(0qGFJl{3 z#!3e$lEd*aA(~ezic$$(OYrY%mn#J)p(zPU{mhYe3$<5$f^A1gs&yqE96}zeO}Xfq zP44e$Yx`YfMnk4xuH%Fc@lBn^;t-+Sg96)~-VOFykAcnJ&zfxfv{-N!Qk~ZDxN1p8 zrVt`{*G?gb8BOrmHgYlc`i=g9QF^!eiaIl>)Td^k6gEXfW2}VYDR@vIQ;`c~`c(!j zhKt6+=A>*_S~!VzDyqnEg&Fkir1dxV314)dQZyb6Dk&gyGQIpoKEd)rcpfLHvx#UjjXw|dtB`m4@z`0qX z{$j1V;M{K-STKjE3`?A>^Cl))c8`y&Bd4V~!oo;1gT2m1@5Y;e;s#R>gef&OH3RY; z;aK(J>#i(bURzqejDaKJNwA7epkGYUMs18oU1_#*5bwn#!!d%&gVoSw=&r|Zk0b?` zGiq7=UcUSJORu6q|7>O3tDl7^&|ppZC7!9S7jy|{$j zr8D6e4tI=N--6NlPxOWWKHr_x5b?qBpCo_E8!%;fMy?%JsS=Q3uI`_1B!O_ zgdQP65(Hic{KV4bh2Es8Q%hynM12K${Ac>0P~=CEV4@G#39XMc4$HY2{&h#DW*3O zog2|-ZVp#aFTQSR?aJDmOG~#GPH^E(YB)`qG&+cZZXRI5U>|2N!;}ruwTDXKEnPmp ze7OW3hQ%37!D2VHjbr^-0&#|-J3JgNF3T>WX&Y1ky^Rd|xQwEH-(czx6fZ4aT3&s9 z5!NNFb3L5+#*tHINE!pu<>kf8SBiix_maW>=8jApk_tf!AmkxfU0!~}(n=AQ41t}+ zY6wJ|riT%1d1di(5#0Gkw>Fd{D-m{@hhxN`t}dQiD8k~9k~I<)vSZL|ZE^9n<#4e1 zf3qA8L@w{%DOSr;M$W0l^npf=Py$756AB5B2Kx|zg;fy9H2d(l2On)m0X1RSeirh5#(ShnBPE{{AoV!gQgjrr6^_}%!G+C98pNJxznt9J3`eW%qg$M0J$Brge{uwWL9 zPbAhvT9nj1s7^bqt2olx>fWl?9$mV!ac=FkE9chM&bBspdQ_@mVhNJxF*7AYPI zYX)V(OAM80B(5?sk73MistScozm@&L4nO+nrDeR3ZoORTvAs4963Dev^5ZPPmbCDXLtYO3}xXnN*^$RX5dw!$uNE+<^OO zlLCxa(yC>7T;A*5*0Tm-z*#~nF2_^!tO}&UF8Z(XC4N`HsW&QsT?!*rkd^&fI>2< ziTo%l?k-x4CbPEpVaZ;}&6!RQH^59YNp|Nfrp@#;iEy^xxSdRQPva+85X6c|3!LMbCp!hl}&`HxAI+5Hjzj?cr=jR|TGk-#LbCl<70FSl9cc-~zXhhSfu zo@RC^>PdgPoR#TaDOEC)D0GV(2IZ0`n&LjkjxBtQPgAu?ws~UF2pv$trCj@ga7GHd`Iu0Vy&AiP3Jhn$xt52WPvzN|nwBaTK?^m<{Zl_cRrK z;b}e^n=*G6c93o!`5y0~GWyOobj7CNe@cdIw8;eh4|L)>g83FA&XM`C*^wYwJpw4K zj|~_V50B|rTh%C>vZ@Eft?qfe1q=UFJ+n`OJ-8{kN>Lk*lxd>g+w66C99>)GA_-2= z%^3eftW~558Q{=WS`-UYx}KzvczKlnk~JQ!0(^MKI1)hts=2e)DEY7nIk^GXb$^3O zdYK7|1}I03fVNNQb9mD6meY*2Y2`BFq_;2OA(RZd$4lP$5*i;6H280Roy==ApaI8% zgJ{PCFsjmPT^3|=N&Z3)KrL<}iku1Cjv{bElwLhjBs6$PQuGM=HAxkW@j9vbf)gIq zAQSLRi4JRg5r+0x19^CP0=!0bUO$CSI*CX{5;L!EjyD88m}AO`c({uEjBZ5+Aq~Wb zr`Biiwq%ozx@fdelw2VU>O!jXW@~WbgHRgz>ws$jOh2T6MP`O(4{8bV2;ZV%jMg9b z$!=mbflC&*z6>}$^kUz8BQ&rFTWy|Bag#IK5?P!Z^PDTnJZu%`aG}iFC%9VC6eP!B zaJvVSC|~kixA_=yyq)U~criDUp=vp+mI6J@a&}a}4&IjW1TV_WHj5cCqMN{$ylT6r z1BfLoxCC;o=8Ygr1mXndgs=6Bx2azuHeh8J9oT-QsG^%HT>y{9sH3Eg0|g0UMoo2t zyD;s;_=7&&#Q_&3cGvRsw0s1^D&g-&YdZeb^5ssK;YfIJt1}l+lv&37Wa#9L{Vf>e z9yTCnA4QIG)srM6S%OCsd6q^IYhRFfUb*Y7M3>}z-`Ly zZ48HGZ7sOp6$3hGjH^LP#?r)YnO!FA&hQd`ivvQv2OrLnm&GdntVPKeYlktxkNJ=fE5IZfpj{@V_E&C$V=rZF4_4KGUtnS3PjCBMNhJd9~I{F)QZ16p? zeXw|FC74GbpW>ED5x`gMsLGI33wQDW6^C_qsm@&l>wUys$0B)5L?g5)CemGKwrX6+ zD<=zeNB>(s#4sM$G^H2_VP(fs%pb>D2P8jCA+bZAwV}$c= zZu`yqE$!Y|RDwCyvo-^BM$pNjQ4!(NxQ&PjO|zmi$Jo*|e!P%>`QAp;-Y^KlC~&lE zEW&cJ9%&LqNN)Z=HCP zkPi@M!7zZ80*`Pk-eXb5DeoprG{y=<*+-FyNMawulrB*KN*b*sBqqC~Oj>ez!zc|8 z1ZB!dyOY9(jv}+PHvtZkqm&UR2RL79C2dq-^6J_*pA-tC*uJid^LVRQ+|) zo%HXDf(!e*$S0x&^=}Fd`KRg^@zA*TRQQC59f2Rck8as~!miItzXq`g8fFqLd5d|D ziB&WwiOTUon~r;;ChLguC7ov!Wn}xbfHTJ7Y((um8E zNS!p2+U4>1OQba`l3fzo1 z(@}%r;j~dN)WoFRWX7mqk(Jc7m0yVPXw`QCw~@%g1AvUsr48}Zd4x3M)INB{`;orMRg+^OtGT<8b7fF@tG(^R%R$yP zik(k!4@{R^o#JH&Ugn3=MW~JLg%!!Jdv4!i*oxatPJXr-O5SR9o4vkFYT%UTmw5FO zA)_mk_TgkwI9{S3)Dp)BBRx zlup;uA|f~0i*YXH)&(WLnmghvjE!A26Su7e z5D^074a15A*BSa{KO^Zl)$D+ijgoS-t#;-F(06Kgg3SxA+IW@aY${in0x_oNbz!P4 zxlPM0G`X`X9fMR`0jVjv(G`j>!z?4x+yVhvQPPg-@(x4@Jqjt*vD(}|r3RwJRmkPH zamk$UF$fr+x!ve9dBc1jTV|`xpbVu721RVWgLYx+9omex zC(`%z+S#+*;4I7{!f@({al`6V86{#T&vbxL)>FKeH85=t3$jB+vuQH%TrpvF+XlGT z!Zux&2AxjTrLuO>+Y?U*;TtMEGbX2jweooXFG;^t*1}N7m6I)sPLZ+;EgT|6yvybR zUop!&=HaSd_2Z(q8Oo%?Cp?{gh$iSbOr2ItAxg`cc>|`YEEqK?eqC@~#NjRg+*OCA}cP-1+0k0 zGF4)`-$TwM+~-?5f&=z9ZJ~@f$cBtK#+zIGxen&6q$U9RnuC;Di{5Q zB~(+_31}p@_mPY=98_5}RfIn<^ah!~+{qz#mumz;RF?C~8a{zbO|YhF zOl1ujGA!e2MQId(t`{}*j8smck-?sJJBFA<3%mT3^3SRlkOLWey425wficfT$&D5` zuHS&`&r3ueE%|~}*rgx)UPGf@gPQ6ih*5Qr=b5rS8_^v~Wn5_FJ;8pIPwWr}m{^By zmvi-x+$sv6FTEw-{G-iU9Wjy*7-1W!T-U}zcS#Ql_3+|=EGLS)@eUD4zw(CGZW;m9 zRep9+7O|_dp3a`}FfYWq2zd;Tj3si^4mmafh+p>Rz%eN+I$1;tasvHvdEgM_CEW>y zv4I0(HLzDb{WEgvr)@_Z5nf`DR3e2p2wEFKQjIl88-n2tB?($Lr zg>5syZWPITBdZg{rwz)*9e&rgUz*x#iakzLl!B8w-@#uwraZ`E5S&P2eQ7Nf{q!IQ zr$YGCdKr-@KU*?ECem0+B2NFZLC7H`^O9xLX7a6wHDF=|Lnxba&NaCn<8w6}V0$qu z_Kv_rIf0p-&DCuJ_k5cqN*4Vb>}qJT_ohIb+~Df! zVtkpSQDD+ygEA?Oy56oMoe1VfW^XFZ6}Dc?dY#*B8Wo9gzJaLfT=|i^dKJ4{l!-dk zA8ijG+rP2UZ)3p-8(2(;B5&wy5AOKMO>I4TgY28!@QdE0GAr|%DIGE|^Z+jBSX{Z+ z8!pLl72JoBg^RearNID)i!&A4u=0f_V7^F$@C}5?_Fx75;D%Z_K*O%n!5qpUE?PTF zpI>pQ$nQNw`TJh-Mg(uD z{a0TKr8&j?U`hCrQat_q0FJDAMG5X!A0~syJ@Z5;Aft+^bc6{lefeqk0!k{s#dNRN zc6zw26G0ubQj^iZgwuhpSUlJ zht#b!4&HaPs3$>j9Yz=49O~#G#ngB+g!;hf+}4CURJWd=(#~Urg0lQoBgNRI3KcOX;A(sS6sH+G&rkO5p~aYol*@R4oqbxdtJICerRnK2eNWp$CR# z8&Oddno0KY#ZT2vRFs-ROJAHQH!RMpPUJP)-=UWlhXvGa5@qPBG@k`lh8FGYEPNqN zzoMyC6HMgLCR`9vXhrM!K5tTzB5vXurls9vV9=}O(S)n3;=|=5ogK;PxqJms2c-5t z5(uC{RF=zL0NgUu-o1;&8m5(H5}HRdtG{VOt^5sG;s7I-DwcFC8NcUi*tf&^ zQY^_F6B)$^1Lh)IY#KYs-s6||zSP6Wx*xd4lfKE)#?uui&Blm$-N!P7gz`zTDwX{^ zG+2UZw(9aCxb_~osAJqplfr|uQA%N%;pQEDXaz~brYs)mt>ne~HX(6^StEGfrNAfe z21QpJ-DTPyG}3?RzYs^m2L$+`zZ@RvDm2n#dU3{!T3A$x;l#P(d@;}T@>o(aago-X zVv;^qP(z2qsW%Bt%1`F~c6t~8JD&`P_&1teVqt~(EgCI&6(^0EMYDNO6#2-b;3UO**IncGw3)*bF-9S zC@-aR^ zwI~4sSXc=qK1Oefo<$T(n9NFWEDer!`}7_5U&{82JV$MTnO*fm;%19&cJL>T;w$nf z0%+rtFC~8wUctD!i%db&yKfNJC0x_yu9a`zgF5e$2FX2rl+Kc0s58BIg>Z;P`?8D8 z(bs*X6THs@GBy~!g+*TyM4{YFRHzj$zHu+G*kABkgOL8PiVpc5t$~Yiwz&U z8spgR8)Catb6L2NmnFl)lKMIq)UZ~?qA(?s+YepY|4Cl4F__$2xUlp7`zyP1xLk_3Zu@dR!)%X2$t!izF-fB$=$VGX|cMJN3$@q&c+-jFJ#I0 z>6#P5JV2%_Bs)X)be;l$B;EVt4NFB)YVUj*@BI9oL^W>9lBR2Pswt0?EGsxo4^D%lCK1t)z;(q~M=(Qz9^~mQ!UA5pDU+rJ{W;0d zv-xL)KxO8|aR?wf08@k^Slm+Z4>0yLne)C5YPaN67ILZqjgERy2I0EhMk6N!WE7uV zx{acmHxZXs4FvmI1@kG~7utznZvlUgvWnfu*L|~~B+;s0vC_I$3DGI#Iz^&OzPxvT zRfAH%O+<_+3c>LK6m-m5Tqm6%;t0`lm|o2Hstz~^jXp0rq;f#t@egp%`A>Dvg+~lh zW@re{Jb!XN{3aZz;}zb9XJ{W<&V!}p0I+EdGQieyd*!gOrCbRKJ=VByv6Y=3qDEzV zZGgGrovrkeuNJcXl_U#)5VNjnrjhJ;SxJ)YeAyk)`$G?z&EyU|q_w~y5IC8n(cTH{ z)!uZhSxGpWd1bf^W=%!{HLb~wn4!e;`+Rj*aiT{dc?pvpv^nz7nt!&g1p;-TA9<@ z;p_XJA zbxc6gePuP#5?`W*aLeq3LATG#2(QV1STewEtbYI#ylFB*fB&Psf;FBvmb-@I{H*bs z#n|{Iynen-6V!UQ%?dl@SMuvUb|YF+T>e__y|{?Xaa8c zeLV53!lInqz&eBB5^-0NRWW`-Sc~e(2*E&B0<4&BIEGl=WqMQzTL|s_wEM$g`R}+m zX62<`II^5KD*$K?YT7~M|B)+(GYn*#vip7u`i(7dDB*`|AaXcVza-)u8EDlVg?p$E z8JM?>+dnRFD}wqSX0+f_IJ`-1{vrb#>}N1w1yDV7o&sVKC!V+nzFP7_7xcZ8!3TKk zdD9iqJM1k6Naj|PII?wKY5Lsmi-FTV^%24M;p|2_$k*JFV{BaG^B9T*~f|TV6gH$<3?3oz`=^YHO6x#x*6mq;q3;Hgjoo@eZ3HB%-2C zV1S~*Y$YSQJG&8)#%s=D9Oytnp|t;VWioE&*7s0LlkJ!Xg2^Xb)Tb)ni1tVlEBmDr zoYMJ!!4K@=e8DFHf6U_$fl*ih$!=3yU}kBi<&KBMWXwN6OrrM!imQ|vzqUriKqm}n zkkj!pZig7>mtU1*$;_WLlq-m03F`vUNs0Qw7P(}GvB@!NY+5NSg-_Zxx zi&lkEAzAR*4|}(Iw-@)eZ|l_;S_v&ANuaGGSy(EEQ;Y;IT(9md@j;x`>|o`KU(@HK z=M9&f!@7W+`1@RBKmqe*~@m+v%$N3p5_Dsj*{t zjvU}rsRV10e1f~izg-=z+I<{G;$XP`6Sy*3$SgV+32Q3leC?DaW0unz3y2i3#<9*hu$r@kKpdNxJ9}rY1(Xl@(C)R zm3T7cR&VKH?-qWcX7A-PqBLYjI1YRQS!}4^Wa=(S6h#edI9D$Gdm&a&JZdwZk-bmwq$i zsGuoLdK$hlSVu0TtgSD-2Ctpso;|wGwZf)8t-l{0;AH5TTgmqrMs))#C%N4@yt)q0g`7ev91dMu!Jq8S5CGfnPC26R z4%iU(ibFJCLzc=G_AAKX{90Bn8oFfu>n|nu)=jMfGj|@lGs5bLLX0c~4!_<=L zFb*6$8@F&F9VVLPF{fgq+0?a)qCLD)!~k5g6;6l8uUA(?dJW*5x>4X9k<5i6St2!Z z1^`Jj#uR>t_XNL^8mxeUbo!S0E9huf#TZ-l=tq4I)b8}|VJrd)218uo2u0DtRgbw% z-<@LbXnqfjAxK-yaW0lr){}6OO0cc)puRJ11Fx^N>8MKvFxE0xqM%bpo%&9$L1-A` zAPux%JR_8G2p_^tn|cH*vAa4@Xq=ylUHEi^JUMv*k$^;EiLAz4L{23&IMJvHX|AHY zG0vjnHQ{5dhi%xu@x)9jtzih3)?L*;Mk^}1ms&TZQ`b*FNR-FopY{%KFFm}gGjv1| z!$a4Xw#c#{&ugXU4 z+k73EWAW2TDhgPYR>DQ08R>Ls^Ag##hSa(u{d{OTFgD;7>8;6D?O~70qtzY`IWOYw zD={-!Dae~B@al3Zh#9cF{4y$YfVjG~@!2@N;sdnf6pP7R>#z+Nin!;RwO&aI)|LAX zX7Snk%Q^~dp_D8|I>iDvri!oh6_RPz!~;_+8fW;SFj!%cNmmw>1rZXzwOYi-fY+sG zZ+c4>6)U%Nae-&mRt>o!hUOhm1$18zS!M4&w}V%%qV$%&6_^~+bYPcvEwi$hQ)SQ9 ziIRk2BK~cKOZ_09qE39_l5?JbJNW!KxKlBeu{WIyPt$r<2Z&uC&;OPA%}b8dCdzp3 zYex8!#FjKp^Y`7HyO}Ea`NqYmM91wh8?fNL+S&$|wCT^(0)z7aCJdbi{CDs6boz!5{Jk=lx8O59EQS4o z&$=83rh&rx8<`7p-vKm7i$~$c0?wiEz;Rh6UhR0}ChaiJXyU{=T~34Pe*5bh*|nghgyid2@)%0HZ8KLUPj2XrrjK4t$Hy1vL*`UmEFD52 zL;#j6y2~?rfQ;_!?QsDm3**F{iEl2)CAwh;&JAW-B)0i#$kt{UF7%U`kdG!G+v@i4 zg}1FA z<&LmYj6Pk2KGC;XlR%Bd`u1o(Z=)KnQ5h!Q_vB^1Q$MGRu|v+OP;FIna`t4z|%~4k{)QngtYQ;Pf>RcL6y)~vgq7Dn2rU!@EO5_sw%~9_R+rqu& z3n=b#V=?X25;s(X^%(sn2*pBw@n4?dcCBlu=z<9w#u~7NLW14NTl``Q;0ZjOzyJO3 zdD87d6E_;&=0jXh)U^c#v9-1?ab{`z=48yeo4P(-Ul^{1GZb?P{wivuR9hl7E-#&w zS2C81tXztd$#6d6osZ673h@;3F4)bnhsJ4Cu-9c}c+Fvn;)z#@0A^S;74*Zj1I#QJ@u^s8)qbu>_ zqv=a{=aPEj>#qAX(lqB$}u!og8^l-GUp@i6(a*A>%hD=WW zaCOEwgkP39+DhP$K3;-FS|R6>W8TRrXNni*eCfX{%*j5*iSpCd0mjI}0TeUY!27cV z_m9NV{ORSNmL7(4E<6+c(NDkNJN)2Fdn<28$ERh z4(z{62)z!!hrDXO*_;Mx9M6S^y1n= z=;rX8L`A&=!qu|hdN=+dUXyF-0o5t_6$2_Et6B<1@NcJ`1q z5La6CLfu#`O%aI|Qt9u|SYs|X>+5#hSIt{mwZIj+mbsfQKZT!a zQ!mur`+hi?B9W>8t2a=$l-)qrhLb%ZsQxYrmTSzcH&}d!n@5BHE216lCM);_QpcsZ zdVG>+%lh#Qd+eDf3zwZSBszOH+b3psYB~{;c6bR9B(D^Q)crM}{OJlsyLzWL(-&rS zbgaqQKzP6sKUe&);6LF_l3IwA!Z}5pPt@CZ*8Hqmq^JnOpOfp~`gf$xaxlJURt*|K zg~mhy|?DFdQ;4)O1mGD4LvU+oV+@^HMj-bQp$ZTCE^3(bvF%o>0FERAGU3 zSh(x&B!W*MEcoSmfqq1#7reX08ByG9h1nBnwDNr7yj-0+m%^$n3`*GLAbv_j z)`FpoQ|xXMAKHHN?Xz(-Q7(|o`+SVd{ilb=!`}C6+iAY1;9Q_gh^R&MIQ6NvH`;!$ z)1l7*3*x+HAQxp{W+t8ZBtB8S@M2;qOO_ur{Pz9<9WDc%bPOV_fmU=FDJbarD~kc` z0upr>7%lRt;=|euG>5_XgkWCB1 z!D}uUliPtY^Ds4|c7;3HP)b1P!c^1&R#9U6Cy^+SnuU`oUiT0`JUAo<*jtSOHNElc zo@Ho<2g(&dN-k!m(Oywb=%%vl?MUqoP&61H@D!;8CdgNRyM!f?2!Y9al|aOf{R`(Q zBL&B(XJ^<@DBFZ!ek67_&k6t|bBHt9%m$~4=nyu6`ApUWIEOiz4i7;s3YCq+!SM(z zAL6CNRfyot#JKM)B?cTs16*Em2WJKJMhw1{>x6`B(D>)0(+k*0;9ndUIO8y3FEdx_MhNGEqZ`up$@Q+T9lswkEp zghB}4l+hMJCQJuAi?0H3aKxE1t=4EXa8(Fgg(JI8Fh(?2;ZM6O%P$`u4&NOgQV zZ$jc-@>gZ61;oW40B-qX0IVBWB7yD5Q!p)IMSCH=B{1~yQv`rw$&7vh7;^W;ds!qv zsAs&9a_va5B5s$+)JR2~W=rZ*=%EQl$5AgGNwZtm#@tNBjrO)?TeMke`2lCApvxx1 zO^r-|2#Q%{9LGQD;NpM?2Q;p(=hLoGm*WgIsD-8Y>A?`Xwz9AMu-7j~r{v*#jk|sh z`wSc{4sqyhbLrTHZZVZcdpYV&%Aoi~C~eUYOJ{)mN7AW5 zNs(wWn;rDMUov*dIl}sbfv3OHnc|;emZ;%ZN@HbkR~4;waZa7BWDQynmovRv?n_1< z&ugNpO3O)VlP{NgA3yf`f6%{W)X)m1)U>2n8JYHy>B->3PAR@6(@^;dvQVn}y_G-w z^!uNeZu5kHf8{-Aj}%RrRp*$bqv~rips4`+FQIdPd97E`O$#vx0wxEpVyZ@5Fo~MI zxr2MZ!dxMyN21(>T!|uU)xU{$v-G!agw&`7ReM1eFX!ynf09r~- zXc}UW!}E6q#LCeKvA%puhv>q>TL4Co(lJ~m-$`^TKcqO#M35@_P|Uy ziN4agyw72Lq@EA@r`sJO-42Gti-3s3;raOoLmiTc3ncfyU*Fn(`urJY8ee1A4PK)UPi2get~QhXYX=hFf1?BtbFi_0s>H6%e&{e}CO5)u^A znj>~e9HFAl-tf*)dRQ{y{>f;N}J8^5{aNRHLq$?hd7>ixp51VX=){&V!+L(=s+@GX2^F>U^ zFD!3OX)g9@DIX3dhn!#6#uF@5#o|h}Hv_6(9|I>N&fXRt!9=W71EP!C)*o#U=L8}r z41|O~nsBiXPE!{7H3X`EkNd{f(j^-Od%yrALLnjeu#9Pt1n(9U7OV=2)yDDi?8??Q zfdL$l5BgSbd7M)a!VQxy8FI5^{{XJ0KCYDHzs4MNlTZ6bD&bWo^(0<8ESMN^*tFqe0)5m}PZe#z+^PQdbCvDX|%D2)i z;2rs83j4T!;rJEyAQ$_HkbSsn*XANQ5OR?2%9RJVmzah0?=K%Nt=ys+8no}Co%4x% z;vshreS2lDGi=86xxDNnE!5(8f(5hZ{aNZQ_x|v2vdp~Gd%n5;3>Wu$zxH?5|GEQC z=fpX~J+L*3rd^XBG2geMZK`r@Yu!ONpk7>z4y#b#oVghErA(vdd&E;$C{0dnZ|t

=uLwwp_d!(bjOxevLldscoHCOsi4sV=vL-I{(fKgxK!Rg@8|u^Y$DiAt z60hq)ct)mj_p)UV-D@Sg;Smvb0?sQQ^)@Qn8XMvj(Tc5*s^4tHqLL zTpAy|9UKjbYMc<%_nyKQ*Hd`$ffjNc2pR<@>I3aNy`zOQ6k>u`kWVL36nUG`?4NWH) zFhiF$CInTlv3ps2GQdm-uctdaJoRqq!fyWtO%es=3Zp8j1_)=~juXvveNDP>-OA~? z=!u2%F@noLUR*N;r?wI_SUHBVUfFU5_F*ypOh^@lvPE5%va3Vw za+z&>nuF5fjoXs`s4Idltf~Jp8J!Pa9VfF$KpnijuBl8n<~yR(Ov3g`Nh`bvU5BXT zAb(MQ?P|wJ%j1AbT{R(e-W^UNuLCV51|fN$Ovlg0=cCu7c0$L(!EzLAap#GxEhaoI?Y$pw<2Z>VqU9h^U>3n8 zD{43*8y_&B<>Vo1V)@tj%)!xoi%OPLv{3{Ct@LrvV(7Q@J$`qhnN&?)fXYc2j6wy~`~(^D5+ywm&U>Bi3b*8ca8Hl99t zw6nhdVrxVCodnUend6In3$N*c`M4MB-SD&rEf@0MCs8z9J~dm{nabRlu<8-t2`UBY zvJV7qN-$#&90DE$!S4*ubWqHffp>HX$&lk&S*-81i7agtFR*24FtS*L&A2?Tb(U2T zHpSL$?1?-Y&O!3Ick`KdL}+3NYl-DClPpTia(Wb2wZ9xgMaOYW+*itr{omTy`z?K8 z_FF)1iZ|>Td>@NEV6Vgsc%Xw=`VS_9lW6|j2;KtO`)mUHUR|ev7mB7)0gYNod9=4M zK0{Qs8zVBruygg`h8ia~`X@sqb;MpTgETjvZ|^Xk^bNvw;GdIs5D%W|U1o?P!d%bJ zaMoNBK;C`7{Ns;5%8ftE5U1hk0g^JocXk5_$EAwYXO@NiR~Mt>!~Orh7*0Om&H?E@ zMiWQW|LU_>yl5HOU!=kp2n5woPyr)OCXjwOIKk)y-1q1ESn$JhT45KX_#d-5-pa%3%g+MK_dGK43VOLr_`SJ>tBt%-1*@@!bTsL+RFLpQtnBoH+Ze zoe+ItUGL{jh~~6n3^A}xxUYU3OimHN@c>FtVF>x*Jh?bN#Kzw_FO*bIaQz}m-Z!Y< z{Z2}jhZzmrsp{YhPVS{-uo~z!{OWFKH{qNR;c&#d(04>i-3a$ikwkGm_Cd zMGUv1rD;{bD>6^nRFUYSmKZ1-a#8@9i{)R3fEipvfjpO%3Wc)(qQ?0#|2R55IKDU> zPE8J@5ze}@pgwzh#Ir|8&xYimr>Srs- z0<%I{ngE#|vCz!`K2uUDAdP35HP*5k6gmCB%PrORHirY;o2-Ajrg$<${*6N@&kqbM z#!yUrbpL%Z!ii2A@}qaqJ4i<;J86Ssypz9jNt&ASFblOlhIy!$EkpG)6|fh@JfsW` z75Ftgs4>)BL5M3)p;g^@-VaDS{z{}#`?>e4Qax+^vWwCR!8diNN4jsyjxCj-{FUg5 z;Sx)eOPJ(_XQEKU>IXSP)tZW*5>FaMy8xl3F=*_E=rj&mw^2vaZwFJPvJOpk!!yPe z*xtWa;G4JSez1Dw&&$frf1?ywX@VKds9DW}$}q9dk*bLOx_|F{j2))Q0H?vf8*sB( ziP48wxBvBn{?}jAQW^fYTA}&`pPbmFScl=;ks6Hs2Yf(Sv3&YUJ{{YqulNbJfPVW7 z%Qp-jNc{BTn46bIe!Ppi-bpSB77tI6NQ=}bd)yY&w@7p`Jf_;@;zB^}za4(?H75JW zx>kzwZKKn+7^XQC0<~FeM-MZFvjPsuhl9<^{STOf5w<3vwXIm<2DaG3(+#W@ZJZx+ z(>JzZ)Jb7TuUH*;y@s`aKJBBv*C7E>-4+=jVgpN?Ac-W-T&YfhO+6i29T96qHz^7X zZviz4QVQVL##oh22Jmg6p+ymmyuX60MCK`%8~qUT1(3a(;lS?Q@@^HPxXb<}ta~Z8 za=-~VhOOgJP*0OlPIMQUQ4-h$#CKlOb2>cciV%H}@Cjvr=_3DtzT3X0L3n#qh0q{3 z^CO@EBX__kyyiCR8K#~U;Iwi&JU9O5&Brd&ai8p4D>7I;RgvlQL#;bA;EksMr=X|E zNl=Gf1X^_i)=-OFYwxOq?Wh)9U&f}@t1JF-Wo#shj`}p}Bp;pvnt`@Kb){bjBSR~IPqlT59)~e&l_QpA1S6c; z(LssPs>Gk4=ZR|j{9c3mLCw*_L4AOK`eb-M%+(^X5g=Qr3f5;+la?q|@NU>3*EvPH zf-#uSYPy9bm*;`OT9VF^tZ|H9Ik@bzW94=$WP4)yK$arINcQ&D3-eZB21 zCYYP0eJ~iF6DMSwr7%mNzg8MgzLSgedwz@vG{an5QNS3MraF*^DmBiZZuN+I%We^IWmd7HWz9Z#T2&9hsOA>Vhe{QH03kCa~EBN!eX!7P=-g zB}_P?f5EILDi01dnfm~|-CBq>qrei0hEfHT)S|4a-3Sbrx?Y@LtJj*HcP4M2$J_Zh zpMwAA;o?H1?`}U>nV5h;IO~h+qqL;kmDj`8Ld5}g3|0oQklEoVvx$10?!-ZrEr7ytzh zq~#@$W9d1O`)W3F(zYX)8OX*dM!%2YV1dDIJtq~&j6fH`24&#tkLJ4=h4E z*SwjM?wN0Rxii;1VG(V@2NYbte4xm~dnDmc%-QSgyx#2xMO% zp22p5&A`A2xT`S?N~)$zF8BMf%kp|k-a>^)o}RR@d^1b+{Qf;{13czpA)qqF7ey6o zdceX&jD^Gb{rj!O@8`vRcSp;o9(P>txW2t}A0bXMN2Flkve0jzcYH%I0q2%#y?ezO3_azSZOk?83ao}ktOPQ6grb-JJf8(seE$+5+E+`N0BzRwkuy9tuu)K2DYU4-)( zAp*$5j{-QBC!^TQY+vB+&dCSBFN#g|11N0JMCfbfj*fuO@X&<+1}x(G(jY>ds}!R6$0F>`5n>mIlfUwkTdMCVaDe%8%KM)H{`KnW+P9CMJzL+{ zf40qjMU$;T{?vFA!|nYUfbKv9LBN}*(^W*p(*n7RpID%9nD8e@IR7R25Nxyir*kZ= zzV83zmcY;bmp8$da}K0qp2WbW}?5&y9+HxS)3+@%;jm-Otd7PYCNO0abH>ri< zP>#@WgP$-5>~UV3nR3<1Mywm%!rxT)Woa-Z!jsqyCN<<<@)lcc@WQ5VrnqJ~JwBTh zM%axXs!iTv8;seff&l^qyQU{+3K5CIY)^&`g|a{cuaf5RXDU{2ZubwM(Q}rhFIRBYJ*4=Nw5D06U;%rDY{8 z9R{DUqNEJi*%2i+4Y~VfmUk{Vf;7U{VF6-WVklu+FFVm#Nz7Vhs1hzAgW+6n7(wGG zfo})K0Db;Cc-g5E1zKE4sDcUT1wx0>zD=``_D&z2EU&^`{wx!rX!-I;QCoK6C}bk0KGqCjVjASKKJI%n@1 z!e5p>McHTsw!?(Cv0?pnA^{UQH3K`#9@3)_6r~<;*QE&f6i0l9dmQEEpl^^k!YV_@2IVl5RsOM+trBtRo)~PbCgUK z9oiNR<07+f0u)SldF{kL4uv=d!UOnUT0U%*WpYS9=ap6t7JpBiuQJ3ccH`WTww)Xm zAPLG>w?HrB4==93eOz;a!<40|-itmaP|gqRrs1XTlU=@#t))L&t%k|eR{8M!5Z4_} z&sUI~!DfDpp#Z0KHl7L3%?KMPBd|U|C3azYf3`gn7H~5#5i`QrTHjcIv@Is#JTQFd z1w0G~w=ibHFeFeP!^Q_1HV3#$2$u>6iy$3Ht@{&hYQ9!iDq$sde-o(rB zMn|JlS-Qf`qRj_W4jr*m+WwhGv$5-rfAHU9IO$J#F7*5jym@kH+fN9hX2eL&*IG+h zK{%}BtS2Td^@2lIdBAX9&$ni$qu1NCYHx7flbv^B4?KH^BRx|H**qJc9wH}dW{+8W z^AYQ>+`tIfv)d#T&cv%3JC*bS_$`_4_H_#*?j4A6+d^+FZMLO0a)Pbvg-SQ?^eji% zv*+tuThF&Rv6D!Wn_2e#(Uh!AuEeQJZ7fIqPLDYd0#zyJzu`HC{vi&9eeD0Faou7k|aXfU42z2e{=2(b_*N4Odgnjg^iq^X;>n zAaR9n6iFBM2`FN(OY=)E5aSFHCIl|uXS7CUq;rW#iVaPMMoR?kF~}%8S@P(GGjcB zNYR@TD%>ZVd0*T%H4ZhXAI|xk%M27Wg0KtdWZ#3Q4YhKvr}E4yMZ-Zda_cd= zM*&A0<^&Tl7grGKr`{*G&Er(W24bDddYPI&&CA+v(5#v1W+bj86*EN_bEU=98X^5z zFxvLkm~)P~yJ(*4U+EN>D+ML+^S`owlC7}JnaFv~|2nE+K%eS1z6l@YjJX!QdRZ5K z3?qix$C_vK*E_(}tbLqjq%IZ5!Cd3|E z9ANqy_;hB3iMD5opUg>TYI(~t&VIAYc?uOTgb}U)(rhd7BRg?jLb2)F9+sO>Wik%G zXI<&>f`CNX2!qr|WGNB|QA4h~*@?o)t9e|V)`7hz7bnP70Q9Shh8{C3StKI#`gKCs z_OJ{9ZGoauNcka}?oN5O!01BoTZQsQ|JCRehZ-d}0Frv)iAPV>>5E`7q}#%#H~+=) z&&hy4>;R;s_0k(QBy2N#zic;hpt!l0S#xlK>|qmun#7#&%bW4Z@C*rf`5XBCh%Dby z3^lNJ7)YY9i6=H9vM6VHRyYANu>p{`H_K;u#S#|EhM&}La@r$w9B=IS2Nj|o#ye91 zY@}ujMNMh8l{O2E``-rJDx?|MWFI7LDXFD~QTlObfZVd@Pe#~Om+`7OiO%Ki^5JOG zL?x*qc8l0rDM+_buos&HMP9WgxU4kt1oJMv^TqyuEuSnO_P$*mt;%6qXtG8-DjOOz zk>GBUfy1UESq*Ze)dC%3CHS41NNSxIljDT+u?lWb7$T7Rpwc_7h-(?NfJH7ey27hz z8XeI~Y}&dJtAtK?g=!aAKJS1R??9iGGUQF{yed*PAwIcDFV@>e8mF~4!-KadVf_ws zMxvrtS7Si##_04d$uN~^gzS@8Zk&ge6j(yGW0Vnha1gnl`jBAk(jUAYO{V9Xh!4l4 zG?U=VrJq#@L)+MoA_Oj;_$x||gfP9_mA5iPRnixiM1)v28MtdnsE|&zg^1=j0>kYK z3DAa_6Pttvk(U_-)yp066@5x{wM#-0ydxA+1V^OYT(A<_fS2x3ArY+Hg6pLjlN#&RjFzS3a|Mhs;5p*S z>Cf!7P%xH&>ny8Hx|lipE*v0KXa;~K8rWpCHQDm)2ug83h%Q5Wo?VXqi_^EK;~!6P z-Lg2?aP>8hOvr`2JtPGfo#HBHT!Tuv2njQdyVy^%EhXu?1sXUa)-fDcATj2`#n{XY z!45@@A~*{SFN5l+`Ic686|dK|vYFANI?tgb3#?jUA0xouC51Xq*CAFx#UZ;rt7u;i zY=GZR;Vfn`zlKcGaAGOwY{X!-X7H;9)`7~cC1M#AT;Sx$Q@#%~|F?tkuSm9(+-YLd zg)=#ZIoK#(;oJ(IMC`S3NVufU zHi3?_dWNG|BZSSPYp9qg>Rnkf!7Sl~`L%CPqm1uPM+p=fKDOmniAWm}eI?e$toWTs zHSrJMr4K`72*~d1NwM~eKu52ye zL^_oJ)4nCPno}XE#^c+~*9a0b^RD^L&d$jQw2Xva$XDhSY&Pt8Ml>9ol~P;+s3t1m zQwJtY>FpC14p~C&!^C93GdD8!p5XTmN-S#6=Suse@>1&{xqi_teY{FW~E7O>kMD9Sh0YcW%qX51HP$>65rZN_6}b zw_@Vb3|LDrpnlrBNhQB`bG5g!vT~=lcN6W|)6aMIPI3NJwEfLHH!6k|5=f0NCUg^E zJ4-ZICNSIbaVMKZyh7-GA!J&I>-sQkV-=-EwMJajSL!7Wfv8^#imL~pm22)GFhk@9 zlaf{;q!)3_Gf6i&#-`f%b#vv$%`TpigQ2$xSJkg^3C0>ZYHWQ1{Boui#3>lW@)<+D zc$J+j9huoe;?PC47z4{4a`K(=z`9?P$tIi096Bv6xkE?-k6opEGyXBQ0qapM{OYCW zW*EY<+Qhb8xcZh?Ss>L0HgUze%*5gd*{meXq-5I=`g%NzhJ8yJqh*F_hc~k_xFw z4|F9ft1_cC&XS8oaVHRx-!v|-ulASpNpdbmq1e<__1@JTnfU-x-=p+CxcPd0h<3YQ zGwtqAR_SL%@8~L)E`<#`I=`8WPu67PGK5EA%}JN)E(V<%(#*Mu#DlIE3>go?uPqyc z-?C+7#qag86u!&o(J>(724P_IW?{t87yt#1U$59w8GA4oHyWO6#aPaV(TDKB0*OS<+jH z6VgYz8sbRL(IHLb&ZmS%7~v%*c$IM^B~He3p*-JQZl_v(v!zeo(N45y;Q|JkCDbC@ z;QnTCfb{&>%gEk3Fq>gYZ*f*O?%m_6^s}+tAp7CF!6|lzv7Me3m8?yhlkvfDI$eK1 zJitH>_htAN!q#}~(JR`~ww(k&4sqg5`LHoHlN>`rv<91;;j?aF&#Y_GjXSuUO|Drp z(06hVb>ZK>(<{1&?}|X;0JkZ{8sPW{{jg7vQm~6(^cUtPorWT?0QaGuI4j!a{t|?< znT|>%cB0C+^x{+3xVG{=nR1z-j%J-%6*+gp0^R=p{scVS-$&H8CVDKZ``n}IQY{@A zq{Gv9i~Y6d-{FojT&=hD{P~XG-K|%)xqBCF%qWe9gPyR)NJk~#5mHP|O|U~pWn9Wq z*;|omFjns4u#+5jf_ZHWNpcktx|MBd+sS057@I^$rb$rW8I{o=603ROuzl;%A7EQN z^Y+d0;3)qT)*adFpzhVv;c3}%%EEt&wW{&2)Jt4lxxz2ZD*tS=oh%0gKO?Z%^atl+ zB{CY-x-M##U}+UurD|VtFyS1XbhQpGoTrmA1e#GH2e{>d11+e-Is3bTgT z9{eTaPU~9Xh18jU^lLMZID&! zs3tZX#E{Wpj#~+IO(mgL>0;8bNiLj}B1Se-QYtdpcAYebM5U}K?P)cA{rO9On{FfO z(p~aG&4PsBXwf-I|&XyZ~CII1VWte{CDxRCC|cw!8V9pS~dTwdp5 ztIlgVW0kMB9{vft#36&;M`&Te*m4La%`S1%ctP{XMxn{s&dC_4*SMR`6eyB*Xm<{^ z5SYzCT278O5jcV$gp9iqoviMl&_ebJsr2IqN<5T8aK*$gDYO(@7^hhc^ZQgN`=^(; z%ON6b={_;${ZVw2EXEdfK<37r#f5B#yP)Yml%%pb;$tl5Xy;H% zW~{bhmP};VqYq`+fVmHUv$#80etmEG573Pzr^BeAUy{C0sHR_m-LbPa5GcAna!AQU zmBq4k5@)8VQCBBWTH>lud}i`n$ogCk?2nP61E-hJa}5zQMQ%%ln*?I*;`!JdvMiwd;Rmk2 z?Lk29E+H-V_U(^*i-^n7dVA=^twe5mE4Ng1^m7oK(3~?*#HSwtErGz8F=F@B!MVuSX1CW3qiLTv`mW1e2u#UfB)h4;uX7E3@PeHkx-V-K5nw_|*zj4-hu$r@eE9CS>yMuF#6(fjiuAB*UfkIBu)??bIc3-& zYmIm$oo71WFyz?mXC=MZT~~QVDjA43dOEgzg0mHih0i1ES^Jaie3XmYmYs}UCpA_} z+_5uGRdzD51Jl2m`ett;$Gb2{TgP}3I>Jakmb|b({Xiy@-n?kr+o6~swbqdwq~7ISoJuG?*DzCAd zTX`Fdlk6M!N*iGKvIN3+?I6;<+6;bRNz!ACleoK@mF6N>oTRd>NUB)S4Mb3;e(~N# z5>7OA72|3$a2{xsy|kc|>|NrM&fa?vRQ6J|SqmnJlas?oEuSrptmUh%=DNOSh$tN& z`E?9IqM&0S&k6O+cMpQShc&<2lP@-h`d>ZUguXsZu= z)+Hd?R#&3u5^P-WA%y`pJ=>rF3x(38NM}8?9zux?0avC84*rS+@!uVo1c*&!X4M8h z9tLdanDS1riRyg8esu|5eRMdAsf;3QEWnP>n$)#8(S`)Rwbc*Q2u3Zh3$}B!4SYf&Vik(D!49~EYONTC5^b=6=M%Hh8xx%y?{jc0 zz-W@nq0GR5bS6U*+OQ^G&*l_cXZ?EP_%fsIK&3j5h({DU5YJyyU3;$FNfA7 zN>R6pi?3?j2=G-_f{njYZs{V-`&Z?Z7O4=~A$jd8Mr*#1bCkl$F2O0hBU0hP;a0GU zP@8mW)UV>TX$we4%NLXhR{n}e`R@*#0>matW$h|P7!&kM1_KU*K%Ntf{FRL~`P_j~ zfY`*yA$jd8Mp$f>0)>^9ze?~*-V(Ls(cxEkm_lzNSb)0I`WwS-Xl+dd4ao;|jUs=zmIZOT1FbRJo-}om8zlB5k3GWCGHAS;aFPzB9_X zBP_wrUlBF`?Jz4G4G^11m9?uFAuyS#a(qad6E4BW-Vh!8*@2BiYa&zBt>W?|hM_a_ zzb?NJlb&Fcyj7_Wg*vcG;WjZ#Kst;ncJU~tdmj_L{1uV%-wv(9wE(M$PFcH(OCw-v zp>!oCxRsxXWBINF*G5}SoZFyPaSy(kEbb+EWn!31;i{4k#62(CfqBtV6Yn~tD%Qc` z3`1q&ixQ1iS8U@;Vj7=1uuLE~F>FGpVux|3#|zvMVb6&aU;s)H;^n_PunG{HIF+@l z7-eS@46D>dn_!l{CU)s_2ZmV-O)P6rs+js|hcKDRZK(-<(QWJ*iaM}zXibC+4C`Jh zcD~#K`wyIpNfOy)+hgXL7*6(5AH{bbZJw2drDB2aSB!zrp!vp}IDhN`nCqV=+SwQ% zyd6$t*KPC;Q!=(P@kZ0}cno7)^Tcrc@GW(zgg%9@NFeftyCw;2%;|%*0hW9Llcofu zE_;%e_(aU^;QAFOlQqd?Z~_X4ngjSy1KY=reQ&3uDW0WnO%X zv_T7!JB)2=Ei6q5o4n%oApx=xZg5*w3*kFNiI6CBDuE$-g=%Lx#2fo?Z89IBFGz0u zMiyE^{fJiV49=JxO4_S@KSC+Tz!VnU?ep0}5qs^2|DE z1&qxYY*loGu^vp_F3}F))cE*axW*bddaex?+11$IWphK=3Fqn;qNpE2VJZl$I4wsK z=*N+VEQF&|=hmWCu`+T_vcQO&eHx6yG*NPDSrAXawo>NOEpe#!bo4oV1ebQ&~al{v+G^$8_hg@jr=d5HXn#U%;4-&Df;R!A&PnHH>c z1~$!=;GCo#*ccy;4nk^<#gJ^EzlKylyl|ZC>fQY9bosZ_n<2Fp@&ftYeMl1#bT?uq zkQuZhJ_Q*qX+Y?og^3x4qQqPS**nb;SkW_8n>`G8QlhGR3q=~0hZ*?UDUMvdR7zl& z#b^eIYX#4Wh5iXx26KM-HIEpX3W&XEd|(mEMa} z2CVna#}NI&p?t<})KNaiD0Fss?+A1SIPDaQuEER!uP27Q(g>{<(wE*XEU?&=< zzJBnKbrIvxlR~_LR)CzlU;u^0o^feNkEu&1LNQcCmX2NvhV z;|QNw7zMhsK=7WgX*qafvOK*)ZQb(@z8un%@OgGY&vayj*U}p3#1`>UVxLWTE9*FE z#8#P%aSJ0=k2_$ugQvwQP`t)RpZvJGBV)%btZZ?>;&^=2TRy#yV-;$0ef`@5MlHh& zJ3u1lH$$vrCr6>xdY_qmR?kETqi}_s$gI-vV0>~iI$y>rPV-}H0uMVvL9Gy@_GNG+ zRpr_6il!jtRKGlcl2BPrq|{iaC0qt1@IF>bierWm6*4^3=zJnAy6R#&o*;5EostSa=oGuPjMLJS>7dRwpz!^5R>;1M8E4 z;fqFWO^h~*4vK}*e9biv-Ud2gxW8&Z1Bx|h&DVs6w+S@II&|KElSK9tL*i$SJMW)T zR5^c%{LX+-^*rHB&RgtJ0s$ue*vlWo6%9pphqw?;oM1^|+8ls3oV><1q2EIvzQg|^ zf2BnNadf5HS|HcGS`7bf0VS8B)M3H=c?!SGNbXyfA{7`YLDKtQSwfDY@I;fU%of_o zKC#g^Kr2C+9`(k`&mfJey{-Z=TC$^*MKiXQAXyUsn`&H()M={&ZW~porQHE8B$jFnKgk+&vjTYfH9q2WKT{;SfsLGBqrWc}QR{Ggc>#Y>oc3Zd; zUWoN@ylNxPx7#D6TB#%;q6-)%V+ot@1_ni>kqANBYE6o-&4WX_8jTz3p%e*MA#mL2 z2_-9~^39e|vIa1yRQNq&pG%k+o}u7}#rQMm*3``&Xi`GK2#i(R8bq$1h^vLp?&)xvUGBx{2$!;TPh$J~=i_Zyai>Rf zG&x7Xuysb0;2&iW*xIBpAN-tIv@b4cM`RXD6xqE^><+>EeG*@}5LB)|_jQw8rhI}F zNzeNm^R;B?cyjMV?bA$V47O8NLHP*54 zdhkdo8O5uiGveW!PcnV9z(EZn;GF38&ZEaqH=geN*FJx3ti#m5=l9jeZ(D14|GVb< z$Ll+f@a2A`%A<|VZ{=I1+LQI~QR{Qma)lnG$0k+ia+Gk_7d=c&O#w0B;osHOh?!yy z;%}{0zi+MK+pFREp!${g`lFZ^a3S7zbXLqM5zfu@ROaiX|v;WXIO!pf1q+> zhhstT+;+sX;v-&?D}UuwknSCe>74BA#zsIm`G_T#x~_;2Uc!=QNQPWk%vTTsU;opc zw0vKeeSs%o<%#Kx$crb~7gG^(M=jO+;6~w~n@!mfclZ=h z6x$4TkvPYWv3!%Yw7_k-eJS|uD)6>~1$6~@Mn(~EOKBr`fG6sDE?FN}U04LBY{NpV ztqwZPbr>q-T#GfTQg(&KnQhEx^2VKN_9=HU_&;>eQ*at-1YpR8$Kp6u?cF!_~b*S*`d+YZKDb?3Sy-mbFURi(B1~Xq=s%| zVB-5TNaP|iS}h}CLy+ts`~tdU1x`5+j;Av&$Ky``H8*G*l=|^+5#MshO?^YNSj_T_qgItuvG_7HUw^ISiNTq-@bo z-ORz1eayQ$l9@V6BN(FlM3)TF^W_2Tgv`)4=B3%ei*$!3;z&FI)B0L!%@*0fE*6?Wf9t7*}uIw+re24@&i!|##zWTVe z_E8MDkAsic);M_c@$C5H*%I>|t*{_6tSl`(M9w35gdw-|5Ls94&&HB97A&5&)9t{| zH=lqCESRHxaFdP{q$K=J$h)Q4=!%Rpx<7;Vmtjdlx;;JqJI_a?cMD*H%d+BE3o&(F{63kY;lol zh*BJC`9N81gw$LgWMqdYh(&5=hxc4@S&K2CT)Nbs9P|;TAqyC6bA%dQ2*KI_fod=o^vTSsk7FRc5A#MN_9vXA4+Z?7unU8k5_NnMoAk z2JPx-s%`5;NE;{=X>X+mMT`H!eeUSvzB#+NBCV*HB_3?Kq2u2qD6q3!u(P2D%K0>K zJ7%@W$Gz?(+AtzxH83t4f-~}ymSd3OoM@IXQbY)<;2*A1l|ZdMT&*RT@{E{{WjbEc zRAV-!Dpf(Lw0mQ%Sihq>{^|amFMeLaT^Q8Nc#;3xuDeED5ClY& zM8m0W`PyqpxxYsO)*CCiu0kR-WKU&8lxQb;#Li+LIjff{WuXJpvR}OHDEnnz_RCNf zYixw>$;<^uNW3C77Z#<lpYn?j*y zo_IMaE^|6{4OBOaIjdJ-3&2ViLi!|380|h<8&ETK+;4*dsQ_pdyrmpi zc$w%@yM8VQeH)^djcUOcgG}b1Fc(;F66$7>vAA;U;gTCJmgKe=0@BL4+%D(3RTmCH zk9o>9L6C`wDTtU94&L+@%M0OTYD|`7FHVQ=5t)EHfW99bUkqc6YX9k}TwT+1en1hx zHBSTC-msa%&I$%r12^#$P|eDc+^<`aw@|PobDW#_Hq1k+{3susE8g;l<=87Grh+KC z_pCS)d`kA;{2&AJHkA~HB=pqB-S@Y^Pim_fFBX0ydP;Hh!IVn^1Q$YpIUEz*2;<`Z z9}%0}`w9+L{CgW2tw9E-Dx}7STNE!5OOQL-p10WT_xO)Dh~bA))NSuGdCO|CLf<9> z)^)yN^;ya(EkG_w*{u$?irpL+2-x6{jukz&1~S7J8jDb4CuNM+DKkQ2R)U(cqK$AtU2BE~5Fgap+0Me^dy=db>T zksp1o7%b7v6^mJl%;3B0hO&P$oE+(umk>sV&R8?(z7Eksh*C7w`4B5`Q(VOpe0Zq=hj>H^|H3Zx>EAidum3Oe| zSk85zImR3czH-gkhj~c0V(X;4Da%8wcI7gMQvy6`BudImkfBEj_OTft$YzpOba5+^ zSfQGmB$ua}Q7GvI!Kc`@QYw}LSK6qO0KNouB507O4Rk^hawV`Op=do?G)5^sj>n6@DCfJnr)uo2u+uz{`Ys?nOO4e+lXeEjU| zj}N|n`0*?J|JPr`*@hVirt1=Cp+u<_<64bYH#<0f8#bB`MM!SccG}50>OmwyJ0@?G zT_iVbsH8sK@LiK@*m){Tv}!PbqyC~19G^rr{t^);rj6FZjL_ncwQ}w?-jrKzKu-Lr z4>tAu&(2(I)Xes*q4HZ&`NHbSN)8w^^c-&ebR#_8XC|mX6l~)vDU>+IvbvV!H3JCZ zYiil-Er3a6T=vqIj@T4Q+L{a*Jw7}=%wNacFhV^1r}Q1AJM^2n?-6|L7Crv_HHLxF z5Wh0Y(U%CJYTia>R1`N9HObANOQ2A%~1e+)t(J3fA| zGp!0x^AZqi9GF>M@SY%bR40Hqsp19sJiCZU#;`e=L^;F8uh~b^@Mc>Y4ym>Y9+KAT*U0>TdgPMx}NbxE=L% zV6q;=`c8Jm4d^-=wkYni>`IADDM&Pth8B81eGcVPC&W5g|M5@N7DxI zW;{{}*3m6d_3@E*mY%e_G@-OgrJ!1`)1U?_?LfVzrHz|5{$vK$B5b&VVps0%3`lzF zqzb7me|8EboJwgz=sJ-CaVz`PRlKFEwZkh7K~Ax)RcL{oZaQ|tObd3xswbXJ(Nj=% zf~84N2#1zPHXP?bEh$~Y4|Fe~B48;=K%9@kfNyubT1&t`9^>gmJQnMKGn<|S(?v~w zPpC5!rKD^JUTOB*83UOJyC>V%aSZGngBHe4$+Lo)KBt?-gV zKAq@g={#GY#nU^TpklEWIPpkPC!AQU1x|MAs1sCHvISZ^cGL+f7HfeMPnvbYiN#vr zK=|~0Qzw{Ess&0oL(~Z+l&V3ghcQMkKs@vWfA*rN|K9jDT}-;z3ljncW-!2UC93*8 z|KSIlN-v1|o!s-pwHip5xL&4`%s=zS=#3@WE12fCdz8IS4ikAh6{9qD(ky6uTu+NV zcBe;_uqrU{iT5V9$tAvC;G8swmsmREd=*Y9oR8`F)J0E(W!0#mjd$8O-lMf}HbY-V zFNz{o6w;TmP=;LMd`x^X;hYsfAx%wq3`HvVHu^vbu35d?!nTT1)!wvK+YzEys%ih) zszsVsszy?@Ro8Vv0fY{INk1cThB~%NiqP9LPjH0i9_dq8M-1 z*!Jxu=O|m0Os)zefpa&svRz5)EtU(>RL7aQnSe`FQ1X^~A$hDylWoW>wse3>Pn_&> zeO2_k>TpVci}No&b6{}*ewHARoWu!0S0ny$`skwhRh{YZ*UPqWC?cB!ULNwqLv2Deib(&f2eXQQX2nfj z5=fzmFnJp;lv?{)l^OfCCJBTPh9fzJ&=rRr`U3Z1aZmw8^@suzNNGHn^wvX!U)kqm zhz6)AD4mzHcE~o%==@iy{1{sMnuDl@mz1u#7)2vF%NM`bEvm@+Tq7-iHQ6 zHBCe4xKS)8Hgu2#dVs*LeV)y%>m`>TB=vbEb!?O}pajSpLOY}Bv+-$C66|j+xoK)P zDINefu@K;1rKOQtEo{%}#!QYIjtbyK6JEl4-Yf06Ns6;r-d^*jXA|sFC?hKN#f+e^ z-33;GIN}P4DCYD;PQNH}3D-rd>l79Ency9!kVK1L0li|x$~Ix3Wuto>9yuvANt^LF zN|O}y>H&Qz-0Xdn^`w7c)@;m~<+R!`=2G1cg_=)RF7K&XW_07oy;S%^i&0f%sPG}5 z4E4(zSos9p=CXNgQT3u0`Mwn`(*JTC81~6~(dY`DF(7M^U$%{>}0YL z9kU#VbELV!iXuBy`tGXF#CjT^L}x(ilzm4@VOm zF_Sn7))Xzxas1hzPTm&ZVBGxv_rGUMH8w^$?yO$yRM4z)grejvc(sKlD((uUQpKwx zxri5-@mN+UlKQxD=ydmo!SdhlE&pNVrNrpsL<~qJbu5@wyXRrl`GlY>3Fbj5r>APWIw<&^U3BA@ zHr|5j6c;rv{zEBE4EEWb4hqjTdiS0T&k(mZJU#fZrS}(Lp_312bGGc2&KMoqQR!`t zaUJ1@F!C$Pt}u*BfY{->)5sVF!yg=Ra{&`mIZ~MF{>2&7wi-O7HW-hOF<0WhAz$_g z4qDuGGCcfla8_&NOoQ(#@NqO6ry8(o=N7BqwlGH<=0(AgU#uUGbsugHby{Z#7BZ?q zw=Rx{+r#tJH?;SZEntQE=;#Pz0fe~I+e6UlsboO{8*cP4nvlC`=f5`B_ctG{{pX`U zXL+0C{eL`vjKPGQK1tb)^+#LJ)}P@0mtZ!G6-!wJQcCVFhI&QU!Hs1cjX>h97tj9l z+4H|V!<_E5OY(N?ki|QMt&e4Q63m)GYC=Mm2f#` z{S|Rx@F6t2t67#%tcn69hJ(YyMfMA+OYmQe7jni`=Vy8r!jAY0&#YOQm=gc=^NaK6 zuOBm8{xmtb_u};J>G((79l-`z9bwW`=F*1XD|4WoxM+4Ro&JQ-vpa1i)StvUamL02 z*@_Y}4@V|Q>SQa+f8<2W4$y^)BsIVwp^m&~M%oepN;KZxzRhY(oevIJb+DC!LSD6W zTzR%D_3TtS8t=Te?{2p zp`K(gWb2u;70x6H~Io)x17m0W$Lwhkj=2@I+(d^YC9W=O5(XiXq~Gp4-;=1a_k zG0QU+=zBHISR`^nzX)(dQ5;LB$bO|_Y#;I>9Sb#qP^tt3Y8DbJ#iRHK==MsQG|E*e zAoXW<`!6GGRbSw|-luil`&Q|9XD=^Ku`t1f+vK@*Oi{ji&vr;kkq_+?D4wb_oMX^7 zrNg_+a%9j;dpGw^Z>Cr$N-Ioyv)T2)nu3%i2)E zBrW)>fGO`*6fFd}$HwqVaD~cn<)j}u>OR3%4yM^QZ~vM+zBmeVXV_ub+k!PVI*G^O zLBmo2Q!8TusSBrrBbmZuWQSO0lZ%7%MI=Aa0_^AxCPK7yFg!cQWwgB?L70;<%)R5$ z+aWTb9SpEMKkU8w&>Qq#T^zwF)T1|lJRMF}K#-LiH#a3JNm8Rt&U@!?hOie;PPo_n zVSLe>3{J6(#mnLQ0j^vaP9`{rE+2*mL+m*tfr1sCu$}_`8?PF`|K6ORpG{Zq-bLP* zHy5u~@cpiri9ayDE}(yx(aHeZ-+=uY5EGs834F<%61Fs`tYNCfBsk9Mv>cMkT|9nP zYAx(8VJZlc@nraVfK*tzKuS&bUuDU}@v5T3)6-C$oLasw_dOQF8Vly*D^Y4z28UKK z)7jdswYnu>95iZ%(H3zq@1=WWW8R$VgnqpXJW|6JdvVG~2a_p{3&-ULKfW1Y)u$)G z@_R+>K$UJuL-t2pdsOS*u(UE3%1f~tT5oxD_)7_dGzC!;a06#9@Y zb2l4@$Gl;5*!7)uECt~x3D-glBkrox8N1X`P}JA57A~0J7RF?JakgRW<;Ri$SC`!u z20nyos*8H0R!NuhIJXoQ*(o2DCX#R9f5kGw3Gr1xn5AhBalJ&v;MA^Bp`u{>baG&b zR1isKS1Y&tfZ7fR^d5h3uFcsaWtH`7XKd`n{wwdWY5VvJAI0Ga(NPA*C8?EkSdkn z$q5M`j%JU&%~@Wxz&3G`I9(wd!M72`(ZxuVs6B`!XVs`dah4RIiWC+hZ$_`N^r<%uS@T-pLRjY4 z3+ohN<;k?fZ1oz-%feYTQ{l{H<7Q(jX?4qvj*ysXhCUlH zsA(s00h@zH@z73E+6tQ#A-EwWRMJ~pF8Q%OyRxeZNN_9*(=k2}@0yKr#}UnD)MQrg zl$Lx>+JNseN*AUW8BntT#zQ21M^YnA{N6S^lKMJ0B64h|wVFx=8_KF_V+O;W zcDV^7etu8_DT&;YNUS8ECSIY-SqEsJ>tFN`2YonkmNZ9$Mw-?(5iH0W)=gA&TH5U^ zyFES=!Oo|!X8rt_1*dq<>I7*HmW1+NZC5dpSOWr78@ z++{>uP5?SMYE9?Og1-^LguU@4xm&FYUU&6>9#&W3mM8_jC#`j{Jt-z!hjyswG8^?B zHhHQ9#FjynM7F}Rm(Eg=2sbH%IB=LDgh*GXcx!jswv2XzB_qs6An89tgZNOc8D_2# zgza%62`hqvl9@K4FvZgx-U&r6_NZfeu`W;)GG$Yns=O>?4gAR2uhG7`$TD4+;X@vL$s{!@gbOkabJ)&XIn zDcPrz8aiFFgO|RX2p%jf9quJz(cT3JR&WtKJ$xypK7Nd~zM88RN-eM7WPov8uZz@ zF^E#GVLU|>FGouiFn4OzwD&eS^NRVN3{o&dI_70|F=65AzMu+BU1ZJy981}6t3zipB*>XEK^AA|*)ejGg;qk*@^DF3MxF)ABtU0Q5k( zn9uG|V-~)y?!fdp-vEm(3P*zP9IeoXQZkc-C8LtsDXomCa<&o3)B z2#)5RoMFQ`=}ZSF#B*u0T+<8HX+wfoty2z^9lZ;~ccbye^oi+x42XiHg~_Hdn$3DN zG-PO*owMx$O$JUaX#2P`*3V&@1TKze#Gt5RwoqCphT1k`g%)qz+?jmfjTpG8kOS)w z(Y!fwh^G;|JecRO6;2T}Grl-_13!W;k|qAf$%OD91%=cR)85S$T#9kNM3+NO#g&^Y zH_gVAS=pMUx!a;3y)_!ebehp>!5C;?sbpp}E{_dWu3W4J*OWXP9}Xc(WO;ue3e0$SestqnxU)<0C0x^_^o3Es=NPi`pB2rpQMNCirS9VQ-JwG+y;G&N!SZov= zS>1Eg2W;4{*z;!ZY~RU|$Fk!Kijb}aL`y0=#LfjU^eeF*Lp7R=_MoGb0>-ujR?83k zXKpf+#45y6g$qZrUkW9{OnxE8ho4AGZwyq9#pS(8wuq@xv7{ju>|3;<6YZFgNc!Ab z@i_wPCVwFoQBQ!}(g7v2pu;;S0*;ZM&I;{DGXC+x2#X}NbQD=BL86pb{rbt{U42igKoOsXi@s0dX@%~H%P<>318T+0ZIrsaAw?yP zQZ*pf3>B$@30o#92pKoZL@!5)^50=Vr*&8$eI^>U{N4)o0Pf=R;ddGe8e+CWFT-YL zQBJfM%nLMD58rhZlT8_WrCeuSgzw($^){6NoLX=d5w4c-?&;PG!pH%9c+sF3kD2P|oR|2%=BX4!10xAQQ4G0|81x} zYf$WTb?+3*oJTy=GG<&Xqg)-tDWXN>^G@;-fwshF@NO_V<`wg-Gyp>Uy*xaXiP!@r z2L}h^$)Qc?h-sZ5m(%I!e|!1hJoEL-Ppj^zeHa!(|zbGhI0vkB^Rr5;c2x z_k4UdItW3tydCK7{?QkYwueu@Ii4KcJw&3YizyX0Go&K|crwJ?kC67`DZ_k44F$CT z0?Uh1#AUNWMY^KSTr5f9tNh=Fmh)AHm9my~Rz8iujdJ+>xINQ+?ALDmX z9@JX6kkgu4CN$-ADPLclI(UH@$|qKXHWNCPXf1yyioooNlmNR=+{mdonm`=rR|c{t zB&*g4-li+93M^J97yxQ^W5W07v!>z<)^xhn$xb%*G~KXqTb>hozIU3lE%VJH5{Le% z&)nK?+oWU52)vZkY$TR*EE9+VawZ6s{s4~h`bOVWGLqZCnzM&HH~QM1zO|T~8_1|g zj_FJNt!G6M*{Mf_=D`h88X~r68SPr%BDH~?h2DfEj{{D&46^bwdeee9Z8nU;HV11O zIPeq$i+#ZCpA60x z17ttefk~00)G^cZ^mw@&Mi+h9cYKjaot_QW1+dv*J^FW$ z&_qy2Uw#Uu5haLzg?DS=?2#ap=!`}qTwYHUqqctfXEpYqf}F{L*nuf@0TtS?GR-7< z(b;4@wQ+@0ZE=K!R!}yPQ3z;T85`Cw>BuT{ebF?#lpq*Ja@wX!s;V$80Q9OQcYPvy z3FKNzm?ZQ&id4|A0z>Z_b3&?r#QF&@M8>QauA~;DzDg;ZsIgt2M)lfDT7gZSHNT3W znCqYtw^(E#_BSHMF!Y()Mv8cGjMc_)(kahMP)l&T#Mc8lrpyzHp%gwOJuP&IG}>T& zW=J(~_~#$eHUg}3HkjjxjZ)+@f|R6ewek9eSnV@Rel>EaV2CSl%m2AjP;dQ=L3oeeM{Y)4V$e!gXzJ}RF$jx^AS_V2j4q-Asru_pzW{Z&?xT%j zf+x}|$dstiE33t8PFelZS^Gc6ub4q4t=+%yp`;n6(Qw&pnhO&BGKJR<1gC2|WXiWr z$V{<`qUUmady#4P;|&0qIpC`QUQ-W*`As{3l9#!HWQJ4tDuR@2z4@dF9(^WyG4^DR zV?DYRzL=O*s0>R3TN+I3VTFUF(~~Cf+Yg&vT>^8|x$d+#FvN9I%X+{9`l3_Ro@N9| zCC-56&33}tk;^`eC>2l{ew~o213>tFY3XOJO!D&%U10#^05}d90@7cpvY`ndS#oBK zu#8T>!Z=IaD7muknC<2pZ)dLU=Fr+H8vhC!yJ>e^R(MJNOm>Z&O`)18Q&v-?6)Olc zsU;Z@FRjaNoJv~>F0-q*DK!I}UqPNXRp^3R&Sko^)vsTW%l7{jgxLhzJ4JV zM1DCN)@&Y66SX;*AS+2rW~q|hE^Y`)#y$a3aHSD2!MaV)V1<`KWOLhBtx<>DwELn( z5_dq|ytQduLC`30U|5aIUkQ%kQEk!aN!@6ghrNs3Tr9~FMgfg(Gfs`nO5WAg9Dtkr z=KH!@{KD+zN;>U0iIrnv_6sKT@G!9I`qf5?NFdhL?Zq?If@Hb<>@>NIT@|x!p4v5o zEzExj`7WzJez_Sitf&5;p95tSkHTFBK|)hy(l*iq-(2!Lv_V^ublpi2yV^hn^y(t; zF>cf~*=*6j8`*`9#pNP_2u0`ra)Fz^d$_YY{LYlcGFD~?ERMM^kqPw~JS+lr7XM~M`1Oy`Fkh7ApbK4dXhvu?I zLZsX`$Acq$>Rdf9SbUuW`+#Qqgm65>pALo0~gM#mAhgFQE0#8+`dK#S8s;`gNy zjx}UV)oS6p(<#<4UbM~kIN%W_Z881~l1h`|EJhdeA>D|nj0out%gM$Gf5;kOi*H6q zd=H*5NW}AKspzO z?}s=jgBGN6+N;y8aG#rrl^Q0B9Fwz zw6vmWfZe`d4qc13XJbwXR8~$V%Y%}i)CN4}8i7@4Zpj$pBBQQ7A`I9@C73*lhf;Bw zd!yL{jzo`oQuc*zQs8`KkWIE03NtPgH7-N4q_pYQvl{f7G(xJ`Qp|*-OF+Y*csNP5uS=C&L$3}L>BT!wdc_N#xpkCR)t zn~7IYS(ELkcekpWF-6|Q5cuU5euXcMJ(>FxyCUEdAmmPU`I!*Fk_X823Z!|~}}A9v#D-n-rN=p0iJde5NsQIN|B zBdq&`r3S{luJL&i46-D{rnmKCV}1YWGi17X_Gn}O(ZKigF^U*!->VWt@ZDof6r=PWS{IRo&B}v&%W7sy0(J?|5h!qwf?_fJl$H~MuFc~ z3p{_k{d{A6M@syAR>II)f8uus?Wbk`kd<|OwjX`BzW)sWL3{Uc#xLoO7kT>KCNO@w zlR>+mQzhK})4QLO#DP58c>0Kax(~8ss}ngOk-owC7_J2J`+!Wu^F;?QiO5zVqvE!p zzl~qzt?@7+Z-C(2A{}eCjSQqqyRq|L0STSp3Xj3nV+<>;~KNVn8+>s3Dcb6xe;FXzlKlC_)M_wQyCYtA|SQe9nDT~%FO{m0SEmhyrhH=$ZI z2D`+A;sn!!|FTP2Eww)%m4;ODY2}f>%T{We9CXCGh34Yqu*{@8_C?!?FRcwh>xB?U zJ{R8Zyu`yX?ah9HH3r9jmxlZSb=p_^a#nGj5VQV}24xK1H{Z8)pVoPcIBv2W=2W5` z^x7w&%?Qb;P+m`)EUDO>T~=+aVtgPIY~5t6F%?9;UU5pTG-tIi8B8`!inUOb_uC35 zGC1gMnt8>06~wJILGU{o)2E@ICE5FQwN@QW)+gBJ;DNlnTOMYfN;FjD>R|(fVzCw_ zv(#Wfg{FE6Ba@a6^@M8|F5+aUmBg2w6tjT@l@7=in*AUe!;#nS-^AlA#3~)&onete zdF7F{0}T_1N@ri~H5jJQy98~Mp9VPp4W2U2mdz3Zatc?a-E%ZulUNHu&dnqWkDzNV@ove<*+7N!M(IgGaC&>yP`2zkT@;GFyj*Ecvr$R_CO+q z?w}`I)F_+Xsn-*_mvi1}%t`5JBC{MEpp})-d(Ocyu5THDmzDw8LtRtHU>}=ci0YwW zQxBS9C@x%UKoiTOZ zuNXj4KdT@v5h#|;CKu4Ju<2X34^6(tW=6GHXP=Mqy?4NczvLb`=wE-^AL^C8 zz<;(O=BNFOS5=|{F0ZA%3R9wdk*gT!yjG`5leDU;Ids?T}`Z zrLPHXX4c5ivmr)oho&Ui*R^9GV`>S0HP#Db8EE+tX|cu=k*hrC9Y4%g4Ldo6hp%Pu z9P7#`GJqx=*Sti<(Y7oP{>1gc&i?o-y_dln<9Abt*)@kAtGPM$4d&=p(BpONmQ!au zsw^0i10SP{9~`EVz9RMx^qPjZJ48xL!Le!127Q4?P?5~EciyfAcumEUVpJAUuV$mJ zaQ&Wx2262ok6vz^pA?Ts0|l5IOZL$qFzTjv(UqJ^MhnzW($K*tnL zg&wPc=$ljEhyV4aKW>W+cVf*UO8OP1))~c!TU#>HHi~Vb?fLMcjRnEqN3u7ECtmUv zGX*Gqw9qy-jebOeqfSgJ_m(Lu#D>2wHw44M(Eo`qvr%!mxfk!b&#LlkOxejh*@~-P zt-fq9=)?=TmUYl`&szk@bO*tXpgx<)w;t~&SQr8eZNL(S!Oe68!FrA0{i59uG0Kn5Z~;Dh<9O3 z#EW9?FB+;1aG4lgg{mUN8{TH{n$SIDo#?zFoX8WtlE@URxW_o*JWjZ#eZeU-PMNjW zw3ZiimI`|pqVmHLkwcC_#46<+OM(HXEjbSM=9@wj!?FTWmm%}WDqPS`v(aBUV=*6j zFj8)vNQNipAAwH3GOI~bAD9!x^UF=ur4psIa;@07q$g!8xtmsYh(91nHm3)pPb8d8 zTSc)>h~Wgm&V5GE{<-NNn44hRusKcowGnQPHlba**qaKolir*>P0xX~ygd<;p$M}# z(})dYb_nxI#zE6-A#N`u4J8O}v8ejyRTuVZHWAAe7&Q=5+n{KzZj`4NXtZ^0m3>rm zHdywh$&}3Y$VM#cs2P0IRAMTddTxJDVV&Heb84Oddf;Ngr*Z!QGEpQQ#QUHO)8Ea- zKG%}H5C_~&q?u~{M-Z%RE2aK)0iZubR$H31SmR=UMa;?5%d@WREQ%+uYfF1~O_xF` zV)HxEa;f?1V{HSAG%6J7JM{)YSBlgE8wQ8#cCG5DDJ{vw=gxaro)|(QQL}v@$WxR= z+a&;%?u80m1F=906n3GOqR!%V*5DyoV9rqsO|AN`vD7*W-9n<+490H2t=mq&25eio zYkwHF7uhpW-y=@w7RhHWbAa(HIfnlR=7=4>rRBzkpt;7kyd74|pNoaRsc1G3SEx5r z2&J4%7OI4T3rU&IYVKM5j~JPxZ*wuEy~-GsE#Lb%1|*m|K{xv^Z(eJ1hU(N);SLR} zA2M%v?U_1}ich${e|kP*`r_>UT1OJyCQ?+`J@XJVi;6a-0+L~Vh_;v1^+^>@-?044 z{%@`Xi@Gc|JfEh3(M9^+4r2BqSWovJjeqMXK~H)dDvZLQH#GgSkXl@MuNEOwQWIal zYZH*Wj@d<7cUm-sVm`!Ak0)Nn?K}4WTgtb2T^N>&%`4u!>4;}tIj@~Bdj`b5o%5y_v<&~H$&Mrym@{qP&{yaKKz`7b(y!~=GLC~T&> zJj3-TQ3Ik&S}IdDDs)zMRU1ds!X)(wo)6Bx!XV_eXgzenuXgXti%^AiFKAim{=peYS<F;1U4b79RCN=17e4hx6Q4Dj(7oi;P|Jwu{mjRqanw4$m{z!KjuDN| zd#RmRJ_V(8KkLFi1vklBNLnD#7!BlUHO+w2_7Eb0zt5yr+29xa)CRLT z)P?-lFGRLlZ}SRb93#q)P{%cv#gZh-OS#!bPcYtO7S)nnu%mZB6_KR&g;8eH-g~W? z+jfF=)ywp*?rfE&nqkYe@sxlVER1W2kj_;~U`{o1RI5ul&a>6?+mm!1r~+I479mQz z7G2@FwULeZMfFZc+J1Ffb#+p+u~wMk&ZM%ucJ#?7#{A_m3PD5#8RsXzXHGg3E>lDm z6U=MdCWF5sf2?og&2F*{HcSpLLOC0O@5}E5WhVSGX)W*@c9q==u-PO!WHfGEklWHL z=_sR%>0JOj9ovQ|te?9elhopKV~%q`o3J87r%_O0i8vj5Qnk z2xP+?n_PFk*Wqxr%kS}IogHIX-{ri79?Fm! z9KMPbDW+#h)Nv6FQZu9ybWdumJSxKb^)x&B=KIY!r9fLKcc7&3DE7#v#TeX)69z!N znSd-{KPL$Cj?NX#z=qxf$7`sIBb&M2(^Q|n6Een3Ed)E|)cEwBMOo7OR)E4ouo4E8CMg5Sf3nLMmrqry3RAZP$~LBS zkI#FM$w+|7ZVe)vFVQpUeovVnQ^9QmHF1UfGzRXC3kF@C{*WLXSIqgC;b5@0d(yNj$$S(Jm%C8|A zlq!nhW<{M>NDX-d#X%ilI(@SmWd(00u~xx9awbdOG};lGEum{tL#mbLwVw5&Ok^IJ zGW$+C2<^rBuDDp)$zMb!yCN^SJfw*;Z6^t>++1zz5g3VfQY}fRJywEhC9XYqQBD{q zcHWd6eETNZbWxnXM)$;rH%v0ISS49O>fU7cfzCqwAcf7`*iuab`&y|LRLggUa%Z>` zY@w~+0d6j0LvLvxmLG(B>lUXTMd)g^m6MB$0??L8$Q<_~GPTH@-I|$eo!YQ$%kLhs znWIITZyP1&zPP+RkTAO}LT0TaM!^@NBq(`mJN34FK9ti*x zi@L?8g$QKWGC8Fs{3K=9WIWptv&=N$jJ8wHma+#$GeSvCK@P*n8DIB&-lRd^7;MWB z`lm%@sFxaoLu)v=0r=r9jbuk1I+o({QWdRxz4O8QogZHa(qQcF;3z*e2%GMp|eO=C)=3x7&Sv(Os(`y6yTojBf~A1 z$pq2LBYQ2%uq=ouo&i8Ivb8j(2N2^BId9KaTA~??zZOXFN)0inZoFxjfS50>NeALv z*%i-0;Lz*KlBY!BXi5X|H;8>cxt`eoN!r!5R6pmQfY=AU5N1p3-H1EeNPn@u!<&y} zgmJ}K0L2TNoZX^z8Be%u113_c&%DEFYrMVoa~O*7$^gT?=Vbi`uHz)M3p#3Rc@VWx zOhQROk0ZY)6F(v?JLegLWu-DEw#H<(G7qd5-jR!|g9`mMCeMFLiKen&e3sN#N>jjD z){WA(mqn|JlpTyh7C58*g0L{|DYmE(xCf!zW}>#>jsy)bEZ;pZT1zFFjOqELfAMN< z%{@GHZmuaiAZfP)z zS&Ebuf>6KtQ`8_$sohNuNV6%lCe3qy z^3$9>6yqnulZ)HG_Tkur*1%J$y2@4KGdb1K&0lfi?5IhOf{<`KBuV{AeBjql=eLLt zbxCQxBsBU+1wL;;?@i){WV|WnwO)e0xp|#dpM}N4@h$T{BR9TMEPoA#A{1j!zlTVX z<M!{O5 zMAXF+?<<0Qls7Fbf6+g^ULT%qtH~FnYFnw^v&4#^{aM@(--GdXru3=@aODxC7dk@r zhgZyHx97o_hKE^7P62`-Mcd*&1Dy}_UaIJ8p8mH}iY8Vax?D+g87T+!)b$*rz<339 zB{p^DX2{A4yav`}hP6$|wEBQvyUd_)wJ&nii!z3kX>_+^Yac^N7>UA0tK{N?3~RWJJ>9^Dsu zbW#VKSvGtj3~N*Xy*7^+qO7u0d=>YB7I9U{~fO6Q!RwKPDNz&^&BfEZP3;a zfJ3w&ktuLUckzTVEXnCzv5)`;B+8fvopofck=uI%)ysGSERF_C2&fXy)_W@}IvSlH zS+SYc4brNj6Imi1N1#F8qRdKntcLpxx0Pg>x&nF&9atVYNo#KxqmkVF!A5UhpWE65 z*HGsKi@r*iE4tQ(OrzJiMvhMBLS5$yrc8^N_qu(yvI0a$$3$p@5R&bYstJdNc#HZo zO$nRPJLoo)B%s`G!)&2mGOo;k&D%q9h-32btn;ipTF!iMmtt%yg|F0_xR)k8pYG&; zGmiJBgo`ckvFL>T3QWwHVARkYL0KhkY6T%v8j?dAbgb0v$bY9fBCD{1S8ko4{jK$b z-KQGSLk$rXW{SLoxMDEe?wNxFHbLpk9LuXSNycnL@~}8TgOKMUV$*E^tt&pR$=GsT zO;<2dARvSf9;`cEd@HVK6236e@=6msF zDDsQPuxNGk5+g2G#asjr-_04!Ft@tQ40`vfC zpJX~q!_T&$^3D{ThKkx4QLmUkOmnW3s>@1IW0bq6PxJIYHx?_ny`CYr-l9Vms)qWd z!d<8!Z>&;j3KB2|E+V59`!aEjh2>5?XQti)(n!=-wJkE*WgbCrRq9Sz=zcE@Ow}_F zJ8WBo*fNll4%?daoKxHezhwjmMg*PB%Pd+tMklj{cmJCv3DYFAUoZjiR?l6KX0i16 zTUE7XTJ>bFOsdVghAkKy!BHZa_;49Iq!`YEDbH(B*@aX`x%m7{=*xbiuK=)2+Jw2m zAH5Luw0f{1LZWi4W5XuAiLyr}fXY{EA?sLrYVJA^@NzpuFep7ypXPU&V(Cme&{tUx zz6n*wvJ@B~{1xZWuL#>wfV6LzgDmO@gQe}J)!aJ8%dp)1n-9I|(1kb(eS_n{V1u3SqaWicLq)q!P+N2kzN9D}-7 zjVEu!2U!HncotPszo@p2*&W}=zFe_z0#D+2O*Gp^mk`?3|B`VdKBAmbLrfpsTwNh5 z23Ax{86{wyi-OF>_k#B5)R^4j5`;^4bC~ff7n7v$v^}{81AA)-G1Jq(hN2J3>H;1O&Tg7!dG9f8gni0G*Tt zH|U)~Ir)jJXoVUj8;Bvb)2Xld&3POmlOZ;H;aC|+st|L2B&O2_i*Ov_J=1EUk9rj&VAHuwaqZARXo`_ctVFk> zHi5Q0-X5c+@0ek{}^c%H)Bz z1FAD&`k)+3G&OL+hklwJX8mq90=)x?P8Ks&yPF9i7y zXGgLUNm(Nxm4~LWM$SgI8W)uBy1B~tr7YY59cZ|PzO*4{ufCDP1RjnlTQvwX(6<_g zf+pm({!4}1n6VuM34I3-G9Z9#I#^jD@@?N*7eR)VLGuYlbNG4u8**$4!Rwh9O$ngw zuwfx5r+qH0{u{wPbn`bCTBi;nfVA7e&4&x#FegHx#GyAzDhc#B(v}9r>%f{H3eq}> z+(H>v&{J#*J2o$}vRT4Q)oUGsp%FX>IKP<+DjYP5MXVe{EF&Dz{ig5SG}_5lEI|*c z2}3twiKi5}!@{tE(iA(=p%t<&_C}B#R!z@2IB|L~NWvay%8cTDE(CZ40EAl6tfUW` zB$m^vST9kUqQ(8U7nHd0#KRe{6fupd4U;x?fEWHrrAtJiKQL8># zRvzH-du_y#p)>6$0B4ws>(RyNr#|xVWM42h{+R70kggDca(+VYWm;$mpR7`i zQx_zpKw0CJ1{bMk1M^KBRLRBao6k(O(`Mv}hH3?lO9|r{U zg9jCO0Yn}tkGu>Hru1dp)K0=ML%{=jzRaAI887N$@!oO?)j zwt&i>=C?v3Rt>ecy`J!#=pT;~|G zDmN(%CY?+87#J)lVT!k-n{h`C)$BF{B)MW*l@+@=^}l%R1D>b7@AmXyy7Y&1MmcJ9 z44zac38%vmZz3`5q}SNp&i+64i$6nl>^!}76)Zu-qIjB4X{ATE*xDD5}I!WAKpzO$VSyDo^v>GC& zVE=;g*@^tm_mpBP7UQ0h^a^X9!z~qE;Iyvb`>G@3paQBX4k=v7kG@lxiwX*G7*J>C zH*5U_eo|*>DP%lhgNbvGbCdpcd<`X#xea1JJqdJcL88BGaM^kh`bjlWs6X?hoblwW z^I*1eb`kaKR4+uz5M=Y(I%^|9Ksb~c5qX8nuj5KtHoW(3T@v7XhJg8?-GKSq?lQ7@ z@FuFjak{{w?d=}={%bS&-jIx+;ye&8O_A%u~4 z@<-Se&)_-*&=v2CC57MZ#pJ8!k@Do4ng-WdgVbz$3nejZpf~uOBBEV1^?Yruv*NWT#?+!XXu zPe%k#kNUt0ao<`1Lf@n2WK7b4KxfjUwH6OGS`@nspA}p|&M~OJYpoyCpn(KC3T01x zxX{Y%8Dke`C;ko35HYNNZyy^VU#TjZLRv0>tpgAx(ilt~o>Wy%C0$T>9$Rao4=u+C ziJuU=NI#WF7)t{ykFre;nMHu=bzJL4rKJ({&aq&f1$4S05O(8r@;~~>QmI5>X>(m7 zmlMvi2=dgGs?Stj4BKLqN9W!w;PCJ2h&#DJ$ixtf*N=Hc6-TcI{HRBhyw?46?Mv}t zxkLX~j0|&}U@Im(JaNfu8yC%5Cm3&m#kTMFa(qApNh*#KB2X`;Lv5LsVJg>(9o6;~ z6RGMHuPTz+%m(Mvz0_6{!!_;!Tq$@=Y^or9x$nk+PMFH+x26f@-mC%sFrTR7rz1vjm$bp&E-&h6x%*He2ZQdX#?4(81MrNV2FhU6!RHGo+c0^KQt< zm^z;Y`)A{LLC$AGWQ?}537&R2Ifugr+R$kQsy*?k91+o;I%m0)M0RM>3k%9M~JHa@W)ScYioI{(eg`xgI}R|KskyxfH7A*7x={*ALfW z^&>Cw14kZp6yNi#0nmP>_$qGY6=a)EV;gh#ASwID{hy(S!^a^VvhmgP>yuNWGC_?s z?D%W|naV=K?SHs@6EAR;`tN@CJE*IgvEc2(AEe={o9knW61a8Rn}-i&vp>AGqmD@1 z;p`5%ICyQ`vmp;6^|1{}kvVw1@<{wqo?}*DX70-_!Bp3q0*rH@ReRim0mf4G@S=-(&P3G-TCk+QDpp z4NcT%Pr=I@Z70sn{;QLl3%C(6=K~P*0PgIJC*ckp5f2E?y5HGQTpPggI_lmU$HAYXq~Yfj?v$P5$mSoN=MszD0hAQ z`i7x!=?ri8@Ek6$kn*(928a^v^l_VVA4idfoqWu&pHH&9Z#F8WxTROh_Z&SAt17q< zrZQsxh?Nf$ecf+h2I&0JE76PAm)M zWe9nOW$+5l?EcyJ+$PNsl0c(@BaOorhx3qRecT_OYF-kyN%!I8T>ND4kpWlIAsBKM zG;Y z?=_pF)r%4Q`o`sHCs7wIiU4tr=cR;q%UY&ZG$faZB9bg-fWGqKYbcwkKbM&b8-*Ql zkre=swF>2|2(lgV{CKr+ljmZ)0inJPNll>go^%VK6r?gx^Kk+&T$ikXGE~d_$95X5 zsD~qB;*nKeIsHjYuCxlz(I6fCCLGz8T+W;gnOGIa?f6Cx0a46$51F1|a6LG=Kz3+w z1N7X#KnTDGg!pfmFiHpb&;l!*+1SxM9S>g05l`7*Ank+lF{M#qG=BY{v-mCepL+-g zOdHuhTkM#h6oj1#p{Y{YoyM!wZ?<-KD2G5AHWDRon=i3TAyE%-^z`zi=WFuX+RpmF zA0KRdxAkNl<;z*@6l<=~UuXqD)3lW)G%`oLUL-CDWUGEln!6NaVhkE;K3 zE8S@Yc#3YeT^&Ld;o* z>Od;%AZP~v7R+ZIol$`>=yW)nqo0N>ZL6_xwGMsID4YAMqkO=@aXTT~P&SO6#V+!1 zOsN3%i%O&(h#S1-D2mU9gXFuc3nY{VAW&iIQjNi#HOsUPYQS=7A`Mll(^&=V3j$f3 zO@|ymArkX?S2u7T+gMOq=H8oDnJrEPZ{RT`kqT^xqBnd39v7)OEQ;{FIssh`UyX>i zB+zkR@cQ9*+fN_w9&i2o;nvfG?cJx#3tz`pXj%3luJ8REIt6>^E?{}hKhsiKl`p^~ z|Ai7)6D40tt~D1HLT3W)tc3!|vEkqET%P^*m{PYK2U#wl5hB?U2(F}N@Zo_wDF#AD+SVbCHQJQN&!ivc*V~w;bDE&DP6bp!|Vos4T&9L5HT=dT< zE!3luny=V2+81M61;~SeH(-XhaG_Bk`0wOmv;Sk&VtV|5-5;Ozf6PFzy@T3tZ(p-2 zb{6AURyrcf*8nV~{?nHX^6r`Pu784T>nb@`b-x{4XJt(HZL6(xSk*rGaX4RXA;7Bg zcdzHG``5u0sJE*8pM$IUYD3p-);>HL&sY2Eysi2k-p*H_%R#ey5_PkFaNgNW&Qn@x zTi%<}-hJ7i7w4$46Sr*7JIP?(QSTSD_EIy|<%tVXwyRj#h~^cFF5nX~_G|48`ULa~ zUpdlJeZO$4kQOO02AaG(S`_Y&Cj<~9hT6%YTdrepO!+* zM!kaQMhy%|Snk+oz)rLOWGvGe}L1>#M5@N!;I+0UU9N{)?&MuVM|@vGLh zvH>bDfJP>8O#`0fQ!;az@di4?{R+mvA4&M6PM)3`rno~mL#tw98J#wJC= zo$y~h3kbcu8TU6G^;@?8k!sDJ3*w$DMr@pYxDYU@-4}rrH6rO9 zJ2)S~O*Da*fE2@hNl1WeiUEffG6&Nw2aSWuZSoP0@)D^;Z?0jddi7ZwY+})By3~^wj`F)*HiKYqfI->5; z5g4WWK<+Wbe^C*GoK}a!Z>b?%o1;Ypq<~4+@q@FQ%gbA_O~r|x#dZuWedEQEfqWSe zIpU$TIAa9J3rpB3LGq6H8sJo~GYN}XZ9$t-MAnefg6yqgu9hYUwLTRZ-b8wHB3eVi zvHb}V(DS%(r6oXp(MOWtY>XTvs0uBmmm8Wr6{Wdlft@Gp<6w%xSXFPR=IcY=sMKE# zM+|AQt!Ld~M8h&{S;+98yEvVV2j%>q&Fw%+8=p5g+{%@_P27_nO@xrzz?Esi&bgg| zhsVd8+xy4I60d)J{CIn33lA*>CuC@wPU!gm@u>)~46#3+jIsV+VeP>o6p(g=9^@

i-Id_gEQe)Kz)LjMGA!C-?%A%if{{r?n42l3<5${7zf{Tlz3#3|}tqQsYN7101dx211 z?POS`@W$2KQOIy9ak*%}HWlBaljwS`8st#^1UyX*h>Z~0i{K2D~{NLR4s5%5R30~b8gLKj@ zCQRZ#J)~-#N-V($Kr+Jq?#(p0BH-qw?@mW_*&gn7Gi&%))uv(XK=$$!@R58pzvi9%^gW*0JUnKnNnke9~ zcXc)Re|^6Y-S`HbkS~yNaq$v+VQ32$8J{%Wy^e<@>qA?!5Euar>$sNq4jo?fb(NY~}kh09r1MHPy zrPoGlrC@S(F+f=EkcVXrFNp(sl&jcBM*e*LCIYp~h$fF20yB#qDeXXyJQ{y}G<-|> zW344Vue-*0ivgs5nISq2n-^ca*eoE%lk2Q z3$c{bSAx~4&$)6|3)vR37bl!`6QEcB%+}t?hf#!aZrGMl6-GHTx-)r8kK~3PF3%C3 zluN2QoC5>#Y-?~RQub)hT5R^iAlm@PWbaU^+e zYgNv1ci{kttGa8V(uoaUm{}&rk5!MifdqU=vlpLj4*4-i)7s-RC)K|xk=jQ`1wZ=l zXf&ry6yR_$n@c0PV~Y!6qKI96_joi0S7fLGikD8W5rv3|AGrQ5WmbQ_wovG`HJ@l6 z^tE>V$a@!lq`|AfscceY;kVK$mO@|ZzU^PbWx}}>TS<}VW057z`2z=x#yEx3TjUvK z7bjRn);eEO!Gr?z2rl2D9_3$MUHuY)yZU+&Cp=CrCcF`c>z{cAzv^fxG6s=_A_qH3 z4X_f%Pv|U?+YW~_LJDf}YjeiR(P9F~_A3%kDs%JkYwZ4YJ~sD{)h)YL&o!$KhBuJF>9lj%xS-N- zC>Ym}6r33;h9GW9AtwFeklGP>%=P8f$++KR5Sj(ogC2jo`uXZZ=oj5bUzuw>rh+)d zE--uHJ_dc%q$GEFGZ@Pq*F?7HrG^NLwZQkuDw{!69S*6Uteqfxr}4&v5yEw^SD+Dr z!M>oA?w>!uuimsOgs#k=0C)4K)`R3ZZ~6aH359G=N7J-mJQHj#v&z z9~|OVh9}2QcMrE8|Htv;_3fQ!`&(;sVncB_b3PMJ7`85ccpnTy)@d8q*f=80v*o~Q zgKquV;djUTTmSZKdw*+lO$6EJ4=tZ<-iQe~&p$Z8cI99q2bgdw0I~0uB{;?PE|N4s zj_^+uL&A&2Zs*zlj+_?9Mh`BMxIwHhWS%aOt4G8C7~RBX#EsD(2u^!DNGDid?{=-j|rwtsAAPI&pm(ukDjs#!tONR74LV6Kwul0`+_wG4eY?=_? z2N8-f;TY4wA?Dyq1nr>4@|w7-VKT^OMA9_a`N`x>PrB`No>>9JI0Hs(4NoRNjmGo= zWWb{Q`ta;KoC;hYPMEOCD(_iotE|Xo2QY>>B4}KH72U&u4iyY^;-KND_X-GHg@Z!S zR;idz)J)H&bnA=IT_`GMkf1nt54Qhv>v;P>DbQMUu0y`Z#DO5h`ZLhvMepcpZ-4jO z{jGy3-INIi1AO-A4@?80+pV&$>Oj47rUDj;S(e82KR9u$AZB8v^T06|^*`=l6;di? z^Pc-PZ|o|9Kb-l91qe`M$QnT!?F3M

&sc&YV^OBtLs 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 I&t5$Hf6wo5eE=')) { + if (getenv('PHPBB_NO_COMPOSER_AUTOLOAD')) + { + if (getenv('PHPBB_AUTOLOAD')) + { + require(getenv('PHPBB_AUTOLOAD')); + } + } + else + { + if (!file_exists($phpbb_root_path . 'vendor/autoload.php')) + { + trigger_error('You have not set up composer dependencies. See http://getcomposer.org/.', E_USER_ERROR); + } + require($phpbb_root_path . 'vendor/autoload.php'); + } require_once 'test_framework/phpbb_functional_test_case.php'; } diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index 76fed76fae..2b6a6aaf29 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -36,8 +36,6 @@ class phpbb_functional_test_case extends phpbb_test_case { self::markTestSkipped('phar extension is not loaded'); } - - require_once 'phar://' . __DIR__ . '/../../vendor/goutte.phar'; } public function setUp() diff --git a/vendor/goutte.phar b/vendor/goutte.phar deleted file mode 100644 index 20b7166a6717aa5b904859fc1ffe3b32106d55a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 267414 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 L-+lYd^KbrtSl@Ba From d88411e10de898d8ad58b1adf9dcedf0f170b67f Mon Sep 17 00:00:00 2001 From: Fyorl Date: Mon, 16 Jul 2012 17:45:43 +0100 Subject: [PATCH 0262/1142] [ticket/10981] Modified functional framework to account for goutte changes PHPBB3-10981 --- tests/test_framework/phpbb_functional_test_case.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index 2b6a6aaf29..e1a45a1bc4 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -46,7 +46,10 @@ class phpbb_functional_test_case extends phpbb_test_case } $this->cookieJar = new CookieJar; - $this->client = new Goutte\Client(array(), array(), null, $this->cookieJar); + $this->client = new Goutte\Client(array(), null, $this->cookieJar); + // Reset the curl handle because it is 0 at this point and not a valid + // resource + $this->client->getClient()->getCurlMulti()->reset(true); $this->root_url = self::$config['phpbb_functional_url']; // Clear the language array so that things // that were added in other tests are gone @@ -191,9 +194,9 @@ class phpbb_functional_test_case extends phpbb_test_case $cookies = $this->cookieJar->all(); // The session id is stored in a cookie that ends with _sid - we assume there is only one such cookie - foreach ($cookies as $key => $cookie); + foreach ($cookies as $cookie); { - if (substr($key, -4) == '_sid') + if (substr($cookie->getName(), -4) == '_sid') { $this->sid = $cookie->getValue(); } From cb9a25ca8048a85ce4a078ad9fe6af16bbaad591 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Tue, 17 Jul 2012 02:28:04 +0200 Subject: [PATCH 0263/1142] [ticket/10993] Use composer.phar from our repository in README.md PHPBB3-10993 --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a7feb8db40..1fc670422f 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,7 @@ Find support and lots more on [phpBB.com](http://www.phpbb.com)! Discuss the dev To be able to run an installation from the repo (and not from a pre-built package) you need to run the following commands to install phpBB's dependencies. cd phpBB - curl -s http://getcomposer.org/installer | php - php composer.phar install + php ../composer.phar install ## CONTRIBUTE From 3637cd395e39c1fa5b7279222abe1da5d2abcd00 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 17 Jul 2012 16:09:05 +0200 Subject: [PATCH 0264/1142] [feature/new-tz-handling] Properly name new timezone selection function Marked the old one as deprecated and made it using the new function. PHPBB3-9558 --- phpBB/includes/acp/acp_board.php | 12 +++++++++++- phpBB/includes/acp/acp_users.php | 6 +++--- phpBB/includes/functions.php | 28 ++++++++++++++++++---------- phpBB/includes/ucp/ucp_prefs.php | 6 +++--- phpBB/includes/ucp/ucp_register.php | 6 +++--- 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 59f7bf707f..159435c64c 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -57,7 +57,7 @@ class acp_board 'board_disable_msg' => false, 'default_lang' => array('lang' => 'DEFAULT_LANGUAGE', 'validate' => 'lang', 'type' => 'select', 'function' => 'language_select', 'params' => array('{CONFIG_VALUE}'), 'explain' => false), 'default_dateformat' => array('lang' => 'DEFAULT_DATE_FORMAT', 'validate' => 'string', 'type' => 'custom', 'method' => 'dateformat_select', 'explain' => true), - 'board_timezone' => array('lang' => 'SYSTEM_TIMEZONE', 'validate' => 'timezone', 'type' => 'select', 'function' => 'tz_select', 'params' => array('{CONFIG_VALUE}', 1), 'explain' => true), + 'board_timezone' => array('lang' => 'SYSTEM_TIMEZONE', 'validate' => 'timezone', 'type' => 'custom', 'method' => 'timezone_select', 'explain' => true), 'default_style' => array('lang' => 'DEFAULT_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array('{CONFIG_VALUE}', false), 'explain' => false), 'override_user_style' => array('lang' => 'OVERRIDE_STYLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), @@ -896,6 +896,16 @@ class acp_board '

'; } + /** + * Select guest timezone + */ + function timezone_select($value, $key) + { + $timezone_select = phpbb_timezone_select($value, true); + $timezone_select['tz_select']; + + return ''; + } /** * Select default dateformat diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 949109abaf..a228e07c22 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1639,7 +1639,7 @@ class acp_users ${'s_sort_' . $sort_option . '_dir'} .= ''; } - $tz_select = tz_select($data['tz'], true, false); + $timezone_selects = phpbb_timezone_select($data['tz'], true, false); $template->assign_vars(array( 'S_PREFS' => true, 'S_JABBER_DISABLED' => ($config['jab_enable'] && $user_row['user_jabber'] && @extension_loaded('xml')) ? false : true, @@ -1679,8 +1679,8 @@ class acp_users 'S_LANG_OPTIONS' => language_select($data['lang']), 'S_STYLE_OPTIONS' => style_select($data['style']), - 'S_TZ_OPTIONS' => $tz_select['tz_select'], - 'S_TZ_DATE_OPTIONS' => $tz_select['tz_dates'], + 'S_TZ_OPTIONS' => $timezone_selects['tz_select'], + 'S_TZ_DATE_OPTIONS' => $timezone_selects['tz_dates'], ) ); diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 4fc2739f33..3533a4ca00 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1143,13 +1143,26 @@ function phpbb_tz_select_compare($a, $b) * * @param string $default A timezone to select * @param boolean $truncate Shall we truncate the options text -* @param boolean $return_tzs_only Shall we just return the options for the timezone selector, -* or also return the options for the time selector. * -* @return string/array Returns either the options for timezone selector -* or an array, also containing the options for the time selector. +* @return string Returns the options for timezone selector only +* +* @deprecated */ -function tz_select($default = '', $truncate = false, $return_tzs_only = true) +function tz_select($default = '', $truncate = false) +{ + $timezone_select = phpbb_timezone_select($default, $truncate); + return $timezone_select['tz_select']; +} + +/** +* Options to pick a timezone and date/time +* +* @param string $default A timezone to select +* @param boolean $truncate Shall we truncate the options text +* +* @return array Returns an array, also containing the options for the time selector. +*/ +function phpbb_timezone_select($default = '', $truncate = false) { global $user; @@ -1220,11 +1233,6 @@ function tz_select($default = '', $truncate = false, $return_tzs_only = true) } $tz_select .= ''; - if ($return_tzs_only) - { - return $tz_select; - } - return array( 'tz_select' => $tz_select, 'tz_dates' => $tz_dates, diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 4239afc96e..f63758c52d 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -131,7 +131,7 @@ class ucp_prefs } $dateformat_options .= '>' . $user->lang['CUSTOM_DATEFORMAT'] . ''; - $tz_select = tz_select($data['tz'], true, false); + $timezone_selects = phpbb_timezone_select($data['tz'], true, false); $template->assign_vars(array( 'ERROR' => (sizeof($error)) ? implode('
', $error) : '', @@ -154,8 +154,8 @@ class ucp_prefs 'S_LANG_OPTIONS' => language_select($data['lang']), 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : style_select($data['style']), - 'S_TZ_OPTIONS' => $tz_select['tz_select'], - 'S_TZ_DATE_OPTIONS' => $tz_select['tz_dates'], + 'S_TZ_OPTIONS' => $timezone_selects['tz_select'], + 'S_TZ_DATE_OPTIONS' => $timezone_selects['tz_dates'], 'S_CAN_HIDE_ONLINE' => ($auth->acl_get('u_hideonline')) ? true : false, 'S_SELECT_NOTIFY' => ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')) ? true : false) ); diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 804bd2e0e2..705000d7a8 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -442,7 +442,7 @@ class ucp_register break; } - $tz_select = tz_select($data['tz'], true, false); + $timezone_selects = phpbb_timezone_select($data['tz'], true, false); $template->assign_vars(array( 'ERROR' => (sizeof($error)) ? implode('
', $error) : '', 'USERNAME' => $data['username'], @@ -455,8 +455,8 @@ class ucp_register 'L_PASSWORD_EXPLAIN' => $user->lang($config['pass_complex'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_pass_chars']), $user->lang('CHARACTERS', (int) $config['max_pass_chars'])), 'S_LANG_OPTIONS' => language_select($data['lang']), - 'S_TZ_OPTIONS' => $tz_select['tz_select'], - 'S_TZ_DATE_OPTIONS' => $tz_select['tz_dates'], + 'S_TZ_OPTIONS' => $timezone_selects['tz_select'], + 'S_TZ_DATE_OPTIONS' => $timezone_selects['tz_dates'], 'S_CONFIRM_REFRESH' => ($config['enable_confirm'] && $config['confirm_refresh']) ? true : false, 'S_REGISTRATION' => true, 'S_COPPA' => $coppa, From 6aea4db6c7adbcee4fffa7cbc39564481fa6e211 Mon Sep 17 00:00:00 2001 From: Fyorl Date: Tue, 17 Jul 2012 17:36:09 +0100 Subject: [PATCH 0265/1142] [ticket/10944] Reverted changes in PHPBB3-10963 is_image now just checks the mimetype reported by the browser and get_mimetype goes back to being unused. PHPBB3-10944 --- phpBB/includes/functions_upload.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/functions_upload.php b/phpBB/includes/functions_upload.php index 33cb585b19..f70e20e616 100644 --- a/phpBB/includes/functions_upload.php +++ b/phpBB/includes/functions_upload.php @@ -151,8 +151,7 @@ class filespec */ function is_image() { - $mimetype = $this->get_mimetype($this->filename); - return (strpos($mimetype, 'image/') === 0); + return (strpos($this->mimetype, 'image/') !== false) ? true : false; } /** @@ -201,12 +200,17 @@ class filespec } /** - * Get mimetype. Utilises the finfo class. + * Get mimetype. Utilize mime_content_type if the function exist. + * Not used at the moment... */ function get_mimetype($filename) { - $finfo = new finfo(FILEINFO_MIME_TYPE); - $mimetype = $finfo->file($filename); + $mimetype = ''; + + if (function_exists('mime_content_type')) + { + $mimetype = mime_content_type($filename); + } // Some browsers choke on a mimetype of application/octet-stream if (!$mimetype || $mimetype == 'application/octet-stream') @@ -338,7 +342,6 @@ class filespec // Remove temporary filename @unlink($this->filename); - $this->filename = $this->destination_file; if (sizeof($this->error)) { From e71474abb5e90d0aeee61d7d9a2d4648aed61426 Mon Sep 17 00:00:00 2001 From: Fyorl Date: Tue, 17 Jul 2012 17:39:19 +0100 Subject: [PATCH 0266/1142] [ticket/10944] strpos now stricter and removed superfluous ternary PHPBB3-10944 --- phpBB/includes/functions_upload.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/functions_upload.php b/phpBB/includes/functions_upload.php index f70e20e616..d4c6b42cf4 100644 --- a/phpBB/includes/functions_upload.php +++ b/phpBB/includes/functions_upload.php @@ -151,7 +151,7 @@ class filespec */ function is_image() { - return (strpos($this->mimetype, 'image/') !== false) ? true : false; + return (strpos($this->mimetype, 'image/') === 0); } /** From ae07e80a6513b7277ba4a6a282df8281d2a37576 Mon Sep 17 00:00:00 2001 From: Patrick Webster Date: Tue, 17 Jul 2012 12:08:13 -0500 Subject: [PATCH 0267/1142] [ticket/10995] Return false in mssqlnative sql_fetchrow on empty result PHPBB3-10995 --- phpBB/includes/db/mssqlnative.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/db/mssqlnative.php b/phpBB/includes/db/mssqlnative.php index c91cc188b0..ff2f369706 100644 --- a/phpBB/includes/db/mssqlnative.php +++ b/phpBB/includes/db/mssqlnative.php @@ -436,7 +436,7 @@ class dbal_mssqlnative extends dbal unset($row['line2'], $row['line3']); } } - return $row; + return (sizeof($row)) ? $row : false; } /** From 7a412f846a9c4c8d712525cf03caa0b5549755ce Mon Sep 17 00:00:00 2001 From: Fyorl Date: Mon, 16 Jul 2012 18:03:47 +0100 Subject: [PATCH 0268/1142] [ticket/10981] Removed setupBeforeClass PHPBB3-10981 --- tests/test_framework/phpbb_functional_test_case.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index e1a45a1bc4..ed8ce9d040 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -30,14 +30,6 @@ class phpbb_functional_test_case extends phpbb_test_case static protected $config = array(); static protected $already_installed = false; - static public function setUpBeforeClass() - { - if (!extension_loaded('phar')) - { - self::markTestSkipped('phar extension is not loaded'); - } - } - public function setUp() { if (!isset(self::$config['phpbb_functional_url'])) From 74074994ba7a1af3ccd017006fc1808c2527706b Mon Sep 17 00:00:00 2001 From: Fyorl Date: Mon, 16 Jul 2012 18:07:20 +0100 Subject: [PATCH 0269/1142] [ticket/10981] Modified travis to use composer with --dev PHPBB3-10981 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6a1ecedac4..1a584add86 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,9 @@ before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS phpbb_tests;'; fi" - sh -c "if [ '$TRAVIS_PHP_VERSION' = '5.2' ]; then pear install --force phpunit/DbUnit; else pyrus install --force phpunit/DbUnit; fi" - phpenv rehash + - cd phpBB + - php ../composer.phar install --dev + - cd .. script: - phpunit --configuration travis/phpunit-$DB-travis.xml From aa2f7bcc2c56fdd82cb07e9794d6114d4b2c359c Mon Sep 17 00:00:00 2001 From: Fyorl Date: Mon, 16 Jul 2012 18:45:10 +0100 Subject: [PATCH 0270/1142] [ticket/10981] Added check for PHP version before running composer PHPBB3-10981 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1a584add86..7c7ba36bfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ before_script: - sh -c "if [ '$TRAVIS_PHP_VERSION' = '5.2' ]; then pear install --force phpunit/DbUnit; else pyrus install --force phpunit/DbUnit; fi" - phpenv rehash - cd phpBB - - php ../composer.phar install --dev + - sh -c "if [ '$TRAVIS_PHP_VERSION' != '5.2' ]; then php ../composer.phar install --dev; fi" - cd .. script: From d157c3b3f40d4779a69af691bf8a304d7666c74d Mon Sep 17 00:00:00 2001 From: Patrick Webster Date: Wed, 18 Jul 2012 01:22:24 -0500 Subject: [PATCH 0271/1142] [ticket/10996] Use correct DBMS name in Travis config for PostgreSQL PHPBB3-10996 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6a1ecedac4..2dbc2fc6d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ env: - DB=postgres before_script: - - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS phpbb_tests;' -U postgres; fi" - - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database phpbb_tests;' -U postgres; fi" + - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'DROP DATABASE IF EXISTS phpbb_tests;' -U postgres; fi" + - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'create database phpbb_tests;' -U postgres; fi" - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS phpbb_tests;'; fi" - sh -c "if [ '$TRAVIS_PHP_VERSION' = '5.2' ]; then pear install --force phpunit/DbUnit; else pyrus install --force phpunit/DbUnit; fi" - phpenv rehash From 7ce66deca8dbe8187b86e130e8011be998580b4e Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 18 Jul 2012 14:48:28 +0200 Subject: [PATCH 0272/1142] [feature/new-tz-handling] Correctly update user and board timezones on update PHPBB3-9558 --- phpBB/install/database_update.php | 33 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 360e12e0b6..a3a51324a9 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2613,25 +2613,24 @@ function change_database_data(&$no_updates, $version) // If the column exists, we did not yet update the users timezone if ($db_tools->sql_column_exists(USERS_TABLE, 'user_dst')) { - // Update timezones - // user_dst is 0 if not in dst and 1 if in dst; - // this happens to be exactly the correction that should be added to the timezone offset - // to obtain dst offset. - // Parenthesize here because we operate on this value later. - $active_offset = '(user_timezone + user_dst)'; + // Update user timezones + $sql = 'SELECT user_dst, user_timezone + FROM ' . USERS_TABLE . ' + GROUP BY user_timezone, user_dst'; + $result = $db->sql_query($sql); - // Now we have a tricky problem of forcing the plus sign into the expression. - // Build it via a conditional since there cannot be a portable printf equivalent in databases. - // Note that active offset is not an absolute value here - it is an expression that will - // be evaluated by the database during query execution. - // We don't print - (minus) here because it will come from active offset. - $sign = $db->sql_conditional("$active_offset < 0", '', '+'); + while ($row = $db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_timezone = '" . $db->sql_escape(_convert_phpbb30_timezone($row['user_timezone'], $row['user_dst'])) . "' + WHERE user_timezone = '" . $db->sql_escape($row['user_timezone']) . "' + AND user_dst = " . (int) $row['user_dst']; + _sql($sql, $errored, $error_ary); + } + $db->sql_freeresult($result); - // Use database-specific escaping because strings are quoted differently by different databases. - $new_value = $db->sql_concatenate($db->sql_escape('GMT'), $sign, $active_offset); - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_timezone = ' . $new_value; - _sql($sql, $errored, $error_ary); + // Update board default timezone + set_config('board_timezone', _convert_phpbb30_timezone($config['board_timezone'], $config['board_dst'])); // After we have calculated the timezones we can delete user_dst column from user table. $db_tools->sql_column_remove(USERS_TABLE, 'user_dst'); From 8078a04f3a3816b4515da01eb3635ae8141db26b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 18 Jul 2012 14:52:51 +0200 Subject: [PATCH 0273/1142] [feature/new-tz-handling] Add function to update the timezone PHPBB3-9558 --- phpBB/install/database_update.php | 99 +++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index a3a51324a9..43363babe1 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -809,6 +809,105 @@ function _add_modules($modules_to_install) $_module->remove_cache_file(); } +/** +* Determinate the new timezone for a given phpBB 3.0 timezone and +* "Daylight Saving Time" option +* +* @param $timezone float Users timezone in 3.0 +* @param $dst int Users daylight saving time +* @return string Users new php Timezone which is used since 3.1 +*/ +function _convert_phpbb30_timezone($timezone, $dst) +{ + $offset = $timezone + $dst; + + switch ($timezone) + { + case '-12': + return 'Etc/GMT' . $offset; //'[UTC - 12] Baker Island Time' + case '-11': + return 'Etc/GMT' . $offset; //'[UTC - 11] Niue Time, Samoa Standard Time' + case '-10': + return 'Etc/GMT' . $offset; //'[UTC - 10] Hawaii-Aleutian Standard Time, Cook Island Time' + case '-9.5': + return 'Pacific/Marquesas'; //'[UTC - 9:30] Marquesas Islands Time' + case '-9': + return 'Etc/GMT' . $offset; //'[UTC - 9] Alaska Standard Time, Gambier Island Time' + case '-8': + return 'Etc/GMT' . $offset; //'[UTC - 8] Pacific Standard Time' + case '-7': + return 'Etc/GMT' . $offset; //'[UTC - 7] Mountain Standard Time' + case '-6': + return 'Etc/GMT' . $offset; //'[UTC - 6] Central Standard Time' + case '-5': + return 'Etc/GMT' . $offset; //'[UTC - 5] Eastern Standard Time' + case '-4.5': + return 'America/Caracas'; //'[UTC - 4:30] Venezuelan Standard Time' + case '-4': + return 'Etc/GMT' . $offset; //'[UTC - 4] Atlantic Standard Time' + case '-3.5': + return 'America/St_Johns'; //'[UTC - 3:30] Newfoundland Standard Time' + case '-3': + return 'Etc/GMT' . $offset; //'[UTC - 3] Amazon Standard Time, Central Greenland Time' + case '-2': + return 'Etc/GMT' . $offset; //'[UTC - 2] Fernando de Noronha Time, South Georgia & the South Sandwich Islands Time' + case '-1': + return 'Etc/GMT' . $offset; //'[UTC - 1] Azores Standard Time, Cape Verde Time, Eastern Greenland Time' + case '0': + return (!$dst) ? 'UTC' : 'Etc/GMT+1'; //'[UTC] Western European Time, Greenwich Mean Time' + case '1': + return 'Etc/GMT+' . $offset; //'[UTC + 1] Central European Time, West African Time' + case '2': + return 'Etc/GMT+' . $offset; //'[UTC + 2] Eastern European Time, Central African Time' + case '3': + return 'Etc/GMT+' . $offset; //'[UTC + 3] Moscow Standard Time, Eastern African Time' + case '3.5': + return 'Asia/Tehran'; //'[UTC + 3:30] Iran Standard Time' + case '4': + return 'Etc/GMT+' . $offset; //'[UTC + 4] Gulf Standard Time, Samara Standard Time' + case '4.5': + return 'Asia/Kabul'; //'[UTC + 4:30] Afghanistan Time' + case '5': + return 'Etc/GMT+' . $offset; //'[UTC + 5] Pakistan Standard Time, Yekaterinburg Standard Time' + case '5.5': + return 'Asia/Kolkata'; //'[UTC + 5:30] Indian Standard Time, Sri Lanka Time' + case '5.75': + return 'Asia/Kathmandu'; //'[UTC + 5:45] Nepal Time' + case '6': + return 'Etc/GMT+' . $offset; //'[UTC + 6] Bangladesh Time, Bhutan Time, Novosibirsk Standard Time' + case '6.5': + return 'Indian/Cocos'; //'[UTC + 6:30] Cocos Islands Time, Myanmar Time' + case '7': + return 'Etc/GMT+' . $offset; //'[UTC + 7] Indochina Time, Krasnoyarsk Standard Time' + case '8': + return 'Etc/GMT+' . $offset; //'[UTC + 8] Chinese Standard Time, Australian Western Standard Time, Irkutsk Standard Time' + case '8.75': + return 'Australia/Eucla'; //'[UTC + 8:45] Southeastern Western Australia Standard Time' + case '9': + return 'Etc/GMT+' . $offset; //'[UTC + 9] Japan Standard Time, Korea Standard Time, Chita Standard Time' + case '9.5': + return 'Australia/ACT'; //'[UTC + 9:30] Australian Central Standard Time' + case '10': + return 'Etc/GMT+' . $offset; //'[UTC + 10] Australian Eastern Standard Time, Vladivostok Standard Time' + case '10.5': + return 'Australia/Lord_Howe'; //'[UTC + 10:30] Lord Howe Standard Time' + case '11': + return 'Etc/GMT+' . $offset; //'[UTC + 11] Solomon Island Time, Magadan Standard Time' + case '11.5': + return 'Pacific/Norfolk'; //'[UTC + 11:30] Norfolk Island Time' + case '12': + return 'Etc/GMT+12'; //'[UTC + 12] New Zealand Time, Fiji Time, Kamchatka Standard Time' + case '12.75': + return 'Pacific/Chatham'; //'[UTC + 12:45] Chatham Islands Time' + case '13': + return 'Pacific/Tongatapu'; //'[UTC + 13] Tonga Time, Phoenix Islands Time' + case '14': + return 'Pacific/Kiritimati'; //'[UTC + 14] Line Island Time' + default: + return 'UTC'; + } +} + /**************************************************************************** * ADD YOUR DATABASE SCHEMA CHANGES HERE * *****************************************************************************/ From 515e1662a900526342b595692d73372f4fb7bbf9 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 18 Jul 2012 14:56:42 +0200 Subject: [PATCH 0274/1142] [feature/new-tz-handling] Remove "timezone might be numeric" As we updated all of the used values, there really shouldn't be one anymore. PHPBB3-9558 --- phpBB/includes/acp/acp_board.php | 5 ----- phpBB/includes/user.php | 6 ------ 2 files changed, 11 deletions(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 159435c64c..47a08b0bab 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -916,11 +916,6 @@ class acp_board // Let the format_date function operate with the acp values $old_tz = $user->tz; - if (is_numeric($config['board_timezone'])) - { - // Might still be numeric - $config['board_timezone'] = sprintf('Etc/GMT%+d', $config['board_timezone']); - } $user->tz = new DateTimeZone($config['board_timezone']); $dateformat_options = ''; diff --git a/phpBB/includes/user.php b/phpBB/includes/user.php index fb6c1552e8..c3bdc71774 100644 --- a/phpBB/includes/user.php +++ b/phpBB/includes/user.php @@ -127,12 +127,6 @@ class phpbb_user extends phpbb_session */ } - if (is_numeric($user_timezone)) - { - // Might still be numeric - $user_timezone = sprintf('Etc/GMT%+d', $user_timezone); - } - $this->tz = new DateTimeZone($user_timezone); // We include common language file here to not load it every time a custom language file is included From 922147f05a75d5a0e00b34f0102bc014583df984 Mon Sep 17 00:00:00 2001 From: Drae Date: Wed, 4 Jul 2012 23:19:59 +0100 Subject: [PATCH 0275/1142] [ticket/10968] Render pagination within the template Since phpBB 2 pagination has been rendered mostly within the source. This limits just what designers can do with pagination. The current form is also questionable in terms of "best practice". The aim is to move rendering completely to the template via the use of a block element. Enabling S_ template vars also allows for control over specific aspects of the pagination output such as next, previous, active and ellipsis. Related to this - merging the capabilities of the topic_generate_pagination with generate_pagination removes an element of duplication. PHPBB3-10968 --- phpBB/adm/style/acp_attachments.html | 21 +++- phpBB/adm/style/acp_groups.html | 16 ++- phpBB/adm/style/acp_icons.html | 15 ++- phpBB/adm/style/acp_inactive.html | 16 ++- phpBB/adm/style/acp_logs.html | 16 ++- phpBB/adm/style/acp_users.html | 32 ++++-- phpBB/adm/style/acp_users_feedback.html | 32 ++++-- phpBB/includes/acp/acp_attachments.php | 3 +- phpBB/includes/acp/acp_groups.php | 3 +- phpBB/includes/acp/acp_icons.php | 6 +- phpBB/includes/acp/acp_inactive.php | 4 +- phpBB/includes/acp/acp_logs.php | 3 +- phpBB/includes/acp/acp_users.php | 9 +- phpBB/includes/functions.php | 98 +++++++++++-------- phpBB/includes/functions_display.php | 42 -------- phpBB/includes/functions_posting.php | 5 +- phpBB/includes/mcp/mcp_forum.php | 3 +- phpBB/includes/mcp/mcp_logs.php | 3 +- phpBB/includes/mcp/mcp_notes.php | 3 +- phpBB/includes/mcp/mcp_pm_reports.php | 3 +- phpBB/includes/mcp/mcp_queue.php | 3 +- phpBB/includes/mcp/mcp_reports.php | 3 +- phpBB/includes/mcp/mcp_topic.php | 6 +- phpBB/includes/mcp/mcp_warn.php | 3 +- phpBB/includes/ucp/ucp_attachments.php | 3 +- phpBB/includes/ucp/ucp_groups.php | 3 +- phpBB/includes/ucp/ucp_main.php | 6 +- phpBB/includes/ucp/ucp_pm_viewfolder.php | 3 +- phpBB/memberlist.php | 3 +- phpBB/search.php | 9 +- .../styles/prosilver/template/mcp_forum.html | 61 ++++++++++-- phpBB/styles/prosilver/template/mcp_logs.html | 42 +++++++- .../prosilver/template/mcp_notes_user.html | 42 +++++++- .../styles/prosilver/template/mcp_queue.html | 42 +++++++- .../prosilver/template/mcp_reports.html | 42 +++++++- .../styles/prosilver/template/mcp_topic.html | 23 ++++- .../prosilver/template/mcp_warn_list.html | 42 +++++++- .../prosilver/template/memberlist_body.html | 44 ++++++++- .../prosilver/template/posting_smilies.html | 19 +++- .../prosilver/template/search_results.html | 63 ++++++++++-- .../prosilver/template/ucp_attachments.html | 42 +++++++- .../prosilver/template/ucp_groups_manage.html | 19 +++- .../template/ucp_main_bookmarks.html | 36 ++++++- .../prosilver/template/ucp_main_front.html | 15 ++- .../template/ucp_main_subscribed.html | 36 ++++++- .../template/ucp_pm_message_header.html | 21 +++- .../prosilver/template/ucp_pm_viewfolder.html | 21 +++- .../prosilver/template/viewforum_body.html | 67 ++++++++++--- .../prosilver/template/viewonline_body.html | 42 +++++++- .../prosilver/template/viewtopic_body.html | 40 ++++++-- phpBB/styles/prosilver/theme/colours.css | 23 +++-- phpBB/styles/prosilver/theme/common.css | 39 +++----- phpBB/styles/prosilver/theme/content.css | 4 +- .../subsilver2/template/pagination.html | 12 ++- .../subsilver2/template/posting_smilies.html | 15 ++- .../subsilver2/template/search_results.html | 13 ++- .../template/ucp_main_bookmarks.html | 13 ++- .../template/ucp_main_subscribed.html | 13 ++- .../subsilver2/template/viewforum_body.html | 26 ++++- .../subsilver2/template/viewonline_body.html | 4 +- phpBB/viewforum.php | 6 +- phpBB/viewonline.php | 2 +- phpBB/viewtopic.php | 3 +- 63 files changed, 1039 insertions(+), 268 deletions(-) diff --git a/phpBB/adm/style/acp_attachments.html b/phpBB/adm/style/acp_attachments.html index c2f8b34792..f3d67d2bfa 100644 --- a/phpBB/adm/style/acp_attachments.html +++ b/phpBB/adm/style/acp_attachments.html @@ -378,11 +378,26 @@

{L_TITLE} - + + {L_NUMBER_FILES}: {TOTAL_FILES} • {L_TOTAL_SIZE}: {TOTAL_SIZE} • + + {S_ON_PAGE} • + + + {S_ON_PAGE} + + diff --git a/phpBB/adm/style/acp_groups.html b/phpBB/adm/style/acp_groups.html index ed94fa985e..ca68bc7528 100644 --- a/phpBB/adm/style/acp_groups.html +++ b/phpBB/adm/style/acp_groups.html @@ -239,11 +239,21 @@
- + + {S_ON_PAGE} • + +
diff --git a/phpBB/adm/style/acp_icons.html b/phpBB/adm/style/acp_icons.html index a8864d42f7..6f42165414 100644 --- a/phpBB/adm/style/acp_icons.html +++ b/phpBB/adm/style/acp_icons.html @@ -255,7 +255,20 @@ -
{PAGINATION}
+

   

diff --git a/phpBB/adm/style/acp_inactive.html b/phpBB/adm/style/acp_inactive.html index 0889eaf400..12cc11d742 100644 --- a/phpBB/adm/style/acp_inactive.html +++ b/phpBB/adm/style/acp_inactive.html @@ -10,11 +10,21 @@
- + + {S_ON_PAGE} • + + diff --git a/phpBB/adm/style/acp_logs.html b/phpBB/adm/style/acp_logs.html index f1c770d33b..3b2d448a44 100644 --- a/phpBB/adm/style/acp_logs.html +++ b/phpBB/adm/style/acp_logs.html @@ -12,11 +12,21 @@ {L_SEARCH_KEYWORDS}:   - + + {S_ON_PAGE} • + +
 

diff --git a/phpBB/adm/style/acp_users.html b/phpBB/adm/style/acp_users.html index a8794176a9..02cdab55b2 100644 --- a/phpBB/adm/style/acp_users.html +++ b/phpBB/adm/style/acp_users.html @@ -157,11 +157,21 @@ - + + {S_ON_PAGE} • + +
@@ -196,11 +206,21 @@
- + + {S_ON_PAGE} • + +
diff --git a/phpBB/adm/style/acp_users_feedback.html b/phpBB/adm/style/acp_users_feedback.html index aa92353807..6b7761ed46 100644 --- a/phpBB/adm/style/acp_users_feedback.html +++ b/phpBB/adm/style/acp_users_feedback.html @@ -1,10 +1,20 @@ - + + {S_ON_PAGE} • + +
@@ -44,11 +54,21 @@
- + + {S_ON_PAGE} • + +
diff --git a/phpBB/includes/acp/acp_attachments.php b/phpBB/includes/acp/acp_attachments.php index abe304c282..4b621cb18d 100644 --- a/phpBB/includes/acp/acp_attachments.php +++ b/phpBB/includes/acp/acp_attachments.php @@ -1222,10 +1222,11 @@ class acp_attachments } $db->sql_freeresult($result); + generate_pagination($this->u_action . "&$u_sort_param", $num_files, $attachments_per_page, $start); + $template->assign_vars(array( 'TOTAL_FILES' => $num_files, 'TOTAL_SIZE' => get_formatted_filesize($total_size), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param", $num_files, $attachments_per_page, $start, true), 'S_ON_PAGE' => on_page($num_files, $attachments_per_page, $start), 'S_LIMIT_DAYS' => $s_limit_days, diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php index 607254adb5..ded28252dc 100644 --- a/phpBB/includes/acp/acp_groups.php +++ b/phpBB/includes/acp/acp_groups.php @@ -682,13 +682,14 @@ class acp_groups $s_action_options .= ''; } + generate_pagination($this->u_action . "&action=$action&g=$group_id", $total_members, $config['topics_per_page'], $start); + $template->assign_vars(array( 'S_LIST' => true, 'S_GROUP_SPECIAL' => ($group_row['group_type'] == GROUP_SPECIAL) ? true : false, 'S_ACTION_OPTIONS' => $s_action_options, 'S_ON_PAGE' => on_page($total_members, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&action=$action&g=$group_id", $total_members, $config['topics_per_page'], $start, true), 'GROUP_NAME' => ($group_row['group_type'] == GROUP_SPECIAL) ? $user->lang['G_' . $group_row['group_name']] : $group_row['group_name'], 'U_ACTION' => $this->u_action . "&g=$group_id", diff --git a/phpBB/includes/acp/acp_icons.php b/phpBB/includes/acp/acp_icons.php index bfe17c5007..fdf0fbde2f 100644 --- a/phpBB/includes/acp/acp_icons.php +++ b/phpBB/includes/acp/acp_icons.php @@ -927,10 +927,8 @@ class acp_icons } } $db->sql_freeresult($result); - - $template->assign_var('PAGINATION', - generate_pagination($this->u_action, $item_count, $config['smilies_per_page'], $pagination_start, true) - ); + + generate_pagination($this->u_action, $item_count, $config['smilies_per_page'], $pagination_start); } /** diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index f098b772ee..c7f9e5398e 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -288,6 +288,8 @@ class acp_inactive $option_ary += array('remind' => 'REMIND'); } + generate_pagination($this->u_action . "&$u_sort_param&users_per_page=$per_page", $inactive_count, $per_page, $start); + $template->assign_vars(array( 'S_INACTIVE_USERS' => true, 'S_INACTIVE_OPTIONS' => build_select($option_ary), @@ -296,7 +298,7 @@ class acp_inactive 'S_SORT_KEY' => $s_sort_key, 'S_SORT_DIR' => $s_sort_dir, 'S_ON_PAGE' => on_page($inactive_count, $per_page, $start), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param&users_per_page=$per_page", $inactive_count, $per_page, $start, true), + 'USERS_PER_PAGE' => $per_page, 'U_ACTION' => $this->u_action . "&$u_sort_param&users_per_page=$per_page&start=$start", diff --git a/phpBB/includes/acp/acp_logs.php b/phpBB/includes/acp/acp_logs.php index 6b67175220..43cf15cb4d 100644 --- a/phpBB/includes/acp/acp_logs.php +++ b/phpBB/includes/acp/acp_logs.php @@ -129,13 +129,14 @@ class acp_logs $log_count = 0; $start = view_log($mode, $log_data, $log_count, $config['topics_per_page'], $start, $forum_id, 0, 0, $sql_where, $sql_sort, $keywords); + generate_pagination($this->u_action . "&$u_sort_param$keywords_param", $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'L_TITLE' => $l_title, 'L_EXPLAIN' => $l_title_explain, 'U_ACTION' => $this->u_action . "&$u_sort_param$keywords_param&start=$start", 'S_ON_PAGE' => on_page($log_count, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param$keywords_param", $log_count, $config['topics_per_page'], $start, true), 'S_LIMIT_DAYS' => $s_limit_days, 'S_SORT_KEY' => $s_sort_key, diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 17687b05c7..e98c015f8b 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1120,10 +1120,11 @@ class acp_users $log_count = 0; $start = view_log('user', $log_data, $log_count, $config['topics_per_page'], $start, 0, 0, $user_id, $sql_where, $sql_sort); + generate_pagination($this->u_action . "&u=$user_id&$u_sort_param", $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'S_FEEDBACK' => true, 'S_ON_PAGE' => on_page($log_count, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&u=$user_id&$u_sort_param", $log_count, $config['topics_per_page'], $start, true), 'S_LIMIT_DAYS' => $s_limit_days, 'S_SORT_KEY' => $s_sort_key, @@ -2035,14 +2036,14 @@ class acp_users } $db->sql_freeresult($result); + generate_pagination($this->u_action . "&u=$user_id&sk=$sort_key&sd=$sort_dir", $num_attachments, $config['topics_per_page'], $start); + $template->assign_vars(array( 'S_ATTACHMENTS' => true, 'S_ON_PAGE' => on_page($num_attachments, $config['topics_per_page'], $start), 'S_SORT_KEY' => $s_sort_key, 'S_SORT_DIR' => $s_sort_dir, - - 'PAGINATION' => generate_pagination($this->u_action . "&u=$user_id&sk=$sort_key&sd=$sort_dir", $num_attachments, $config['topics_per_page'], $start, true)) - ); + )); break; diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index e40df93194..b7b8ccda0f 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1884,85 +1884,99 @@ function tracking_unserialize($string, $max_depth = 3) * Pagination routine, generates page number sequence * tpl_prefix is for using different pagination blocks at one page */ -function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '') +function generate_pagination($base_url, $num_items, $per_page, $start_item = 1, $tpl_prefix = '', $reverse_count = false, $ignore_on_page = false, $block_var_name = '') { global $template, $user; // Make sure $per_page is a valid value $per_page = ($per_page <= 0) ? 1 : $per_page; - $separator = '' . $user->lang['COMMA_SEPARATOR'] . ''; $total_pages = ceil($num_items / $per_page); if ($total_pages == 1 || !$num_items) { - return false; + return; } $on_page = floor($start_item / $per_page) + 1; $url_delim = (strpos($base_url, '?') === false) ? '?' : ((strpos($base_url, '?') === strlen($base_url) - 1) ? '' : '&'); - - $page_string = ($on_page == 1) ? '1' : '1'; - - if ($total_pages > 5) + $block_var_name = ($block_var_name) ? $block_var_name . '.pagination' : 'pagination'; + + if ($reverse_count) { - $start_cnt = min(max(1, $on_page - 4), $total_pages - 5); - $end_cnt = max(min($total_pages, $on_page + 4), 6); - - $page_string .= ($start_cnt > 1) ? ' ... ' : $separator; - - for ($i = $start_cnt + 1; $i < $end_cnt; $i++) - { - $page_string .= ($i == $on_page) ? '' . $i . '' : '' . $i . ''; - if ($i < $end_cnt - 1) - { - $page_string .= $separator; - } - } - - $page_string .= ($end_cnt < $total_pages) ? ' ... ' : $separator; + $start_cnt = ($total_pages > 5) ? $total_pages - 3 : 1; + $end_cnt = $total_pages; } else { - $page_string .= $separator; - - for ($i = 2; $i < $total_pages; $i++) - { - $page_string .= ($i == $on_page) ? '' . $i . '' : '' . $i . ''; - if ($i < $total_pages) - { - $page_string .= $separator; - } - } + $start_cnt = ($total_pages > 5) ? min(max(1, $on_page - 4), $total_pages - 5) : 1; + $end_cnt = ($total_pages > 5) ? max(min($total_pages, $on_page + 4), 6) : $total_pages; } - $page_string .= ($on_page == $total_pages) ? '' . $total_pages . '' : '' . $total_pages . ''; - - if ($add_prevnext_text) + if ($on_page != $total_pages) { - if ($on_page != 1) + $template->assign_block_vars($block_var_name, array( + 'PAGE_NUMBER' => '', + 'PAGE_URL' => $base_url . "{$url_delim}start=" . ($on_page * $per_page), + 'S_IS_CURRENT' => false, + 'S_IS_PREV' => false, + 'S_IS_NEXT' => true, + 'S_IS_ELLIPSIS' => false, + )); + } + + $i = 1; + do + { + $page_url = $base_url . (($i == 1) ? '' : $url_delim . 'start=' . (($i - 1) * $per_page)); + + $template->assign_block_vars($block_var_name, array( + 'PAGE_NUMBER' => $i, + 'PAGE_URL' => $page_url, + 'S_IS_CURRENT' => (!$ignore_on_page && $i == $on_page) ? true : false, + 'S_IS_NEXT' => false, + 'S_IS_PREV' => false, + 'S_IS_ELLIPSIS' => ($i == 2 && $start_cnt > 1) || ($i == $total_pages - 1 && $end_cnt < $total_pages) ? true : false, + )); + + if ($i > 1 && $i < $start_cnt - 1) { - $page_string = '' . $user->lang['PREVIOUS'] . '  ' . $page_string; + $i = $start_cnt; } - - if ($on_page != $total_pages) + elseif ($i == $end_cnt && $end_cnt < $total_pages - 1) { - $page_string .= '  ' . $user->lang['NEXT'] . ''; + $i = $total_pages - 1; + } + else + { + $i++; } } + while ($i <= $total_pages); + if ($on_page != 1) + { + $template->assign_block_vars($block_var_name, array( + 'PAGE_NUMBER' => '', + 'PAGE_URL' => $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page), + 'S_IS_CURRENT' => false, + 'S_IS_PREV' => true, + 'S_IS_NEXT' => false, + 'S_IS_ELLIPSIS' => false, + )); + } + $template->assign_vars(array( $tpl_prefix . 'BASE_URL' => $base_url, 'A_' . $tpl_prefix . 'BASE_URL' => addslashes($base_url), $tpl_prefix . 'PER_PAGE' => $per_page, - $tpl_prefix . 'PREVIOUS_PAGE' => ($on_page == 1) ? '' : $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page), $tpl_prefix . 'NEXT_PAGE' => ($on_page == $total_pages) ? '' : $base_url . "{$url_delim}start=" . ($on_page * $per_page), $tpl_prefix . 'TOTAL_PAGES' => $total_pages, $tpl_prefix . 'CURRENT_PAGE' => $on_page, )); - return $page_string; + return; } /** diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index 545f75ad67..00efd281c0 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -639,48 +639,6 @@ function get_forum_parents(&$forum_data) return $forum_parents; } -/** -* Generate topic pagination -*/ -function topic_generate_pagination($replies, $url) -{ - global $config, $user; - - // Make sure $per_page is a valid value - $per_page = ($config['posts_per_page'] <= 0) ? 1 : $config['posts_per_page']; - - if (($replies + 1) > $per_page) - { - $total_pages = ceil(($replies + 1) / $per_page); - $pagination = ''; - - $times = 1; - for ($j = 0; $j < $replies + 1; $j += $per_page) - { - $pagination .= '' . $times . ''; - if ($times == 1 && $total_pages > 5) - { - $pagination .= ' ... '; - - // Display the last three pages - $times = $total_pages - 3; - $j += ($total_pages - 4) * $per_page; - } - else if ($times < $total_pages) - { - $pagination .= '' . $user->lang['COMMA_SEPARATOR'] . ''; - } - $times++; - } - } - else - { - $pagination = ''; - } - - return $pagination; -} - /** * Obtain list of moderators of each forum */ diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index c549f99091..6c21b0f412 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -61,10 +61,7 @@ function generate_smilies($mode, $forum_id) 'body' => 'posting_smilies.html') ); - $template->assign_var('PAGINATION', - generate_pagination(append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=smilies&f=' . $forum_id), - $smiley_count, $config['smilies_per_page'], $start, true) - ); + generate_pagination(append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=smilies&f=' . $forum_id), $smiley_count, $config['smilies_per_page'], $start); } $display_link = false; diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index fec1edc872..936f85ccc2 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -101,6 +101,8 @@ function mcp_forum_view($id, $mode, $action, $forum_info) $forum_topics = ($total == -1) ? $forum_info['forum_topics'] : $total; $limit_time_sql = ($sort_days) ? 'AND t.topic_last_post_time >= ' . (time() - ($sort_days * 86400)) : ''; + generate_pagination($url . "&i=$id&action=$action&mode=$mode&sd=$sort_dir&sk=$sort_key&st=$sort_days" . (($merge_select) ? $selected_ids : ''), $forum_topics, $topics_per_page, $start); + $template->assign_vars(array( 'ACTION' => $action, 'FORUM_NAME' => $forum_info['forum_name'], @@ -129,7 +131,6 @@ function mcp_forum_view($id, $mode, $action, $forum_info) 'S_MCP_ACTION' => $url . "&i=$id&forum_action=$action&mode=$mode&start=$start" . (($merge_select) ? $selected_ids : ''), - 'PAGINATION' => generate_pagination($url . "&i=$id&action=$action&mode=$mode&sd=$sort_dir&sk=$sort_key&st=$sort_days" . (($merge_select) ? $selected_ids : ''), $forum_topics, $topics_per_page, $start), 'PAGE_NUMBER' => on_page($forum_topics, $topics_per_page, $start), 'TOTAL_TOPICS' => $user->lang('VIEW_FORUM_TOPICS', (int) $forum_topics), )); diff --git a/phpBB/includes/mcp/mcp_logs.php b/phpBB/includes/mcp/mcp_logs.php index 848bad40a3..2b5e847e26 100644 --- a/phpBB/includes/mcp/mcp_logs.php +++ b/phpBB/includes/mcp/mcp_logs.php @@ -171,10 +171,11 @@ class mcp_logs $log_count = 0; $start = view_log('mod', $log_data, $log_count, $config['topics_per_page'], $start, $forum_list, $topic_id, 0, $sql_where, $sql_sort, $keywords); + generate_pagination($this->u_action . "&$u_sort_param$keywords_param", $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'PAGE_NUMBER' => on_page($log_count, $config['topics_per_page'], $start), 'TOTAL' => $user->lang('TOTAL_LOGS', (int) $log_count), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param$keywords_param", $log_count, $config['topics_per_page'], $start), 'L_TITLE' => $user->lang['MCP_LOGS'], diff --git a/phpBB/includes/mcp/mcp_notes.php b/phpBB/includes/mcp/mcp_notes.php index 99dbb8d86d..7e0dd69a2f 100644 --- a/phpBB/includes/mcp/mcp_notes.php +++ b/phpBB/includes/mcp/mcp_notes.php @@ -215,6 +215,8 @@ class mcp_notes } } + generate_pagination($this->u_action . "&$u_sort_param$keywords_param", $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'U_POST_ACTION' => $this->u_action, 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, @@ -226,7 +228,6 @@ class mcp_notes 'L_TITLE' => $user->lang['MCP_NOTES_USER'], 'PAGE_NUMBER' => on_page($log_count, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param$keywords_param", $log_count, $config['topics_per_page'], $start), 'TOTAL_REPORTS' => $user->lang('LIST_REPORTS', (int) $log_count), 'RANK_TITLE' => $rank_title, diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php index d242929a80..3e2ce4bfd3 100644 --- a/phpBB/includes/mcp/mcp_pm_reports.php +++ b/phpBB/includes/mcp/mcp_pm_reports.php @@ -298,6 +298,8 @@ class mcp_pm_reports } } + generate_pagination($this->u_action . "&st=$sort_days&sk=$sort_key&sd=$sort_dir", $total, $config['topics_per_page'], $start); + // Now display the page $template->assign_vars(array( 'L_EXPLAIN' => ($mode == 'pm_reports') ? $user->lang['MCP_PM_REPORTS_OPEN_EXPLAIN'] : $user->lang['MCP_PM_REPORTS_CLOSED_EXPLAIN'], @@ -307,7 +309,6 @@ class mcp_pm_reports 'S_MCP_ACTION' => $this->u_action, 'S_CLOSED' => ($mode == 'pm_reports_closed') ? true : false, - 'PAGINATION' => generate_pagination($this->u_action . "&st=$sort_days&sk=$sort_key&sd=$sort_dir", $total, $config['topics_per_page'], $start), 'PAGE_NUMBER' => on_page($total, $config['topics_per_page'], $start), 'TOTAL' => $total, 'TOTAL_REPORTS' => $user->lang('LIST_REPORTS', (int) $total), diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index 4d720a435c..86fffbcf93 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -419,6 +419,8 @@ class mcp_queue } unset($rowset, $forum_names); + generate_pagination($this->u_action . "&f=$forum_id&st=$sort_days&sk=$sort_key&sd=$sort_dir", $total, $config['topics_per_page'], $start); + // Now display the page $template->assign_vars(array( 'L_DISPLAY_ITEMS' => ($mode == 'unapproved_posts') ? $user->lang['DISPLAY_POSTS'] : $user->lang['DISPLAY_TOPICS'], @@ -430,7 +432,6 @@ class mcp_queue 'S_MCP_ACTION' => build_url(array('t', 'f', 'sd', 'st', 'sk')), 'S_TOPICS' => ($mode == 'unapproved_posts') ? false : true, - 'PAGINATION' => generate_pagination($this->u_action . "&f=$forum_id&st=$sort_days&sk=$sort_key&sd=$sort_dir", $total, $config['topics_per_page'], $start), 'PAGE_NUMBER' => on_page($total, $config['topics_per_page'], $start), 'TOPIC_ID' => $topic_id, 'TOTAL' => $user->lang((($mode == 'unapproved_posts') ? 'VIEW_TOPIC_POSTS' : 'VIEW_FORUM_TOPICS'), (int) $total), diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 69c6a4cfff..3c5f51ee05 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -411,6 +411,8 @@ class mcp_reports unset($report_ids, $row); } + generate_pagination($this->u_action . "&f=$forum_id&t=$topic_id&st=$sort_days&sk=$sort_key&sd=$sort_dir", $total, $config['topics_per_page'], $start); + // Now display the page $template->assign_vars(array( 'L_EXPLAIN' => ($mode == 'reports') ? $user->lang['MCP_REPORTS_OPEN_EXPLAIN'] : $user->lang['MCP_REPORTS_CLOSED_EXPLAIN'], @@ -421,7 +423,6 @@ class mcp_reports 'S_FORUM_OPTIONS' => $forum_options, 'S_CLOSED' => ($mode == 'reports_closed') ? true : false, - 'PAGINATION' => generate_pagination($this->u_action . "&f=$forum_id&t=$topic_id&st=$sort_days&sk=$sort_key&sd=$sort_dir", $total, $config['topics_per_page'], $start), 'PAGE_NUMBER' => on_page($total, $config['topics_per_page'], $start), 'TOPIC_ID' => $topic_id, 'TOTAL' => $total, diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index d4ba89b04c..c2f44bd360 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -306,6 +306,11 @@ function mcp_topic_view($id, $mode, $action) 'post_ids' => $post_id_list, )); + if ($posts_per_page) + { + generate_pagination(append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&t={$topic_info['topic_id']}&mode=$mode&action=$action&to_topic_id=$to_topic_id&posts_per_page=$posts_per_page&st=$sort_days&sk=$sort_key&sd=$sort_dir"), $total, $posts_per_page, $start); + } + $template->assign_vars(array( 'TOPIC_TITLE' => $topic_info['topic_title'], 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $topic_info['forum_id'] . '&t=' . $topic_info['topic_id']), @@ -345,7 +350,6 @@ function mcp_topic_view($id, $mode, $action) 'RETURN_FORUM' => sprintf($user->lang['RETURN_FORUM'], '', ''), 'PAGE_NUMBER' => on_page($total, $posts_per_page, $start), - 'PAGINATION' => (!$posts_per_page) ? '' : generate_pagination(append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&t={$topic_info['topic_id']}&mode=$mode&action=$action&to_topic_id=$to_topic_id&posts_per_page=$posts_per_page&st=$sort_days&sk=$sort_key&sd=$sort_dir"), $total, $posts_per_page, $start), 'TOTAL_POSTS' => $user->lang('VIEW_TOPIC_POSTS', (int) $total), )); } diff --git a/phpBB/includes/mcp/mcp_warn.php b/phpBB/includes/mcp/mcp_warn.php index c614beea3b..31822dd147 100644 --- a/phpBB/includes/mcp/mcp_warn.php +++ b/phpBB/includes/mcp/mcp_warn.php @@ -175,6 +175,8 @@ class mcp_warn )); } + generate_pagination(append_sid("{$phpbb_root_path}mcp.$phpEx", "i=warn&mode=list&st=$st&sk=$sk&sd=$sd"), $user_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'U_POST_ACTION' => $this->u_action, 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, @@ -183,7 +185,6 @@ class mcp_warn 'S_SELECT_SORT_DAYS' => $s_limit_days, 'PAGE_NUMBER' => on_page($user_count, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination(append_sid("{$phpbb_root_path}mcp.$phpEx", "i=warn&mode=list&st=$st&sk=$sk&sd=$sd"), $user_count, $config['topics_per_page'], $start), 'TOTAL_USERS' => $user->lang('LIST_USERS', (int) $user_count), )); } diff --git a/phpBB/includes/ucp/ucp_attachments.php b/phpBB/includes/ucp/ucp_attachments.php index 836185f105..fb2dfaabee 100644 --- a/phpBB/includes/ucp/ucp_attachments.php +++ b/phpBB/includes/ucp/ucp_attachments.php @@ -170,9 +170,10 @@ class ucp_attachments } $db->sql_freeresult($result); + generate_pagination($this->u_action . "&sk=$sort_key&sd=$sort_dir", $num_attachments, $config['topics_per_page'], $start); + $template->assign_vars(array( 'PAGE_NUMBER' => on_page($num_attachments, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&sk=$sort_key&sd=$sort_dir", $num_attachments, $config['topics_per_page'], $start), 'TOTAL_ATTACHMENTS' => $num_attachments, 'L_TITLE' => $user->lang['UCP_ATTACHMENTS'], diff --git a/phpBB/includes/ucp/ucp_groups.php b/phpBB/includes/ucp/ucp_groups.php index a7c6479759..d919208aca 100644 --- a/phpBB/includes/ucp/ucp_groups.php +++ b/phpBB/includes/ucp/ucp_groups.php @@ -844,11 +844,12 @@ class ucp_groups $s_action_options .= ''; } + generate_pagination($this->u_action . "&action=$action&g=$group_id", $total_members, $config['topics_per_page'], $start); + $template->assign_vars(array( 'S_LIST' => true, 'S_ACTION_OPTIONS' => $s_action_options, 'S_ON_PAGE' => on_page($total_members, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&action=$action&g=$group_id", $total_members, $config['topics_per_page'], $start), 'U_ACTION' => $this->u_action . "&g=$group_id", 'S_UCP_ACTION' => $this->u_action . "&g=$group_id", diff --git a/phpBB/includes/ucp/ucp_main.php b/phpBB/includes/ucp/ucp_main.php index 00b7b55f27..fed844de96 100644 --- a/phpBB/includes/ucp/ucp_main.php +++ b/phpBB/includes/ucp/ucp_main.php @@ -670,8 +670,9 @@ class ucp_main if ($topics_count) { + generate_pagination($this->u_action, $topics_count, $config['topics_per_page'], $start); + $template->assign_vars(array( - 'PAGINATION' => generate_pagination($this->u_action, $topics_count, $config['topics_per_page'], $start), 'PAGE_NUMBER' => on_page($topics_count, $config['topics_per_page'], $start), 'TOTAL_TOPICS' => $user->lang('VIEW_FORUM_TOPICS', (int) $topics_count), )); @@ -813,7 +814,6 @@ class ucp_main 'S_DELETED_TOPIC' => (!$row['topic_id']) ? true : false, - 'PAGINATION' => topic_generate_pagination($replies, append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . "&t=$topic_id")), 'REPLIES' => $replies, 'VIEWS' => $row['topic_views'], 'TOPIC_TITLE' => censor_text($row['topic_title']), @@ -837,6 +837,8 @@ class ucp_main 'U_VIEW_TOPIC' => $view_topic_url, 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id), )); + + generate_pagination(append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . "&t=$topic_id"), $replies + 1, $config['posts_per_page'], 1, '', true, true, 'topicrow'); } } } diff --git a/phpBB/includes/ucp/ucp_pm_viewfolder.php b/phpBB/includes/ucp/ucp_pm_viewfolder.php index 8b1cd419f4..168280bd96 100644 --- a/phpBB/includes/ucp/ucp_pm_viewfolder.php +++ b/phpBB/includes/ucp/ucp_pm_viewfolder.php @@ -451,8 +451,9 @@ function get_pm_from($folder_id, $folder, $user_id) $sql_limit_time = ''; } + generate_pagination(append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=view&action=view_folder&f=$folder_id&$u_sort_param"), $pm_count, $config['topics_per_page'], $start); + $template->assign_vars(array( - 'PAGINATION' => generate_pagination(append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=view&action=view_folder&f=$folder_id&$u_sort_param"), $pm_count, $config['topics_per_page'], $start), 'PAGE_NUMBER' => on_page($pm_count, $config['topics_per_page'], $start), 'TOTAL_MESSAGES' => $user->lang('VIEW_PM_MESSAGES', (int) $pm_count), diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index ed87b6c448..f837a21666 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -1572,9 +1572,10 @@ switch ($mode) } } + generate_pagination($pagination_url, $total_users, $config['topics_per_page'], $start); + // Generate page $template->assign_vars(array( - 'PAGINATION' => generate_pagination($pagination_url, $total_users, $config['topics_per_page'], $start), 'PAGE_NUMBER' => on_page($total_users, $config['topics_per_page'], $start), 'TOTAL_USERS' => $user->lang('LIST_USERS', (int) $total_users), diff --git a/phpBB/search.php b/phpBB/search.php index 93cd0c31fd..fefe7f9b0e 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -603,13 +603,14 @@ if ($keywords || $author || $author_id || $search_id || $submit) $phrase_search_disabled = $search->supports_phrase_search() ? false : true; } + generate_pagination($u_search, $total_match_count, $per_page, $start); + $template->assign_vars(array( 'SEARCH_TITLE' => $l_search_title, 'SEARCH_MATCHES' => $l_search_matches, 'SEARCH_WORDS' => $keywords, 'SEARCHED_QUERY' => $search->search_query, 'IGNORED_WORDS' => (sizeof($search->common_words)) ? implode(' ', $search->common_words) : '', - 'PAGINATION' => generate_pagination($u_search, $total_match_count, $per_page, $start), 'PAGE_NUMBER' => on_page($total_match_count, $per_page, $start), 'PHRASE_SEARCH_DISABLED' => $phrase_search_disabled, @@ -899,7 +900,6 @@ if ($keywords || $author || $author_id || $search_id || $submit) 'LAST_POST_AUTHOR_COLOUR' => get_username_string('colour', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), 'LAST_POST_AUTHOR_FULL' => get_username_string('full', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), - 'PAGINATION' => topic_generate_pagination($replies, $view_topic_url), 'TOPIC_TYPE' => $topic_type, 'TOPIC_IMG_STYLE' => $folder_img, @@ -1003,6 +1003,11 @@ if ($keywords || $author || $author_id || $search_id || $submit) 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id), 'U_VIEW_POST' => (!empty($row['post_id'])) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=" . $row['topic_id'] . '&p=' . $row['post_id'] . (($u_hilit) ? '&hilit=' . $u_hilit : '')) . '#p' . $row['post_id'] : '') )); + + if ($show_results == 'topics') + { + generate_pagination($view_topic_url, $replies + 1, $config['posts_per_page'], 1, '', true, true, 'searchresults'); + } } if ($topic_id && ($topic_id == $result_topic_id)) diff --git a/phpBB/styles/prosilver/template/mcp_forum.html b/phpBB/styles/prosilver/template/mcp_forum.html index afd4f2308a..a31e0fc74a 100644 --- a/phpBB/styles/prosilver/template/mcp_forum.html +++ b/phpBB/styles/prosilver/template/mcp_forum.html @@ -10,11 +10,28 @@
- + @@ -42,7 +59,20 @@ {REPORTED_IMG}  [ {L_DELETE_SHADOW_TOPIC} ]
- {topicrow.PAGINATION} + + + {topicrow.ATTACH_ICON_IMG} {L_POST_BY_AUTHOR} {topicrow.TOPIC_AUTHOR_FULL} » {topicrow.FIRST_POST_TIME}
{topicrow.REPLIES} {L_REPLIES}
{L_LAST_POST} {L_POST_BY_AUTHOR} {topicrow.LAST_POST_AUTHOR_FULL}
{topicrow.LAST_POST_TIME}
@@ -72,11 +102,28 @@
- + diff --git a/phpBB/styles/prosilver/template/mcp_logs.html b/phpBB/styles/prosilver/template/mcp_logs.html index b5561687b2..af101f8d24 100644 --- a/phpBB/styles/prosilver/template/mcp_logs.html +++ b/phpBB/styles/prosilver/template/mcp_logs.html @@ -12,8 +12,25 @@ {L_SEARCH_KEYWORDS}:   @@ -62,8 +79,25 @@ {S_FORM_TOKEN} diff --git a/phpBB/styles/prosilver/template/mcp_notes_user.html b/phpBB/styles/prosilver/template/mcp_notes_user.html index afe904dab3..ea484e5628 100644 --- a/phpBB/styles/prosilver/template/mcp_notes_user.html +++ b/phpBB/styles/prosilver/template/mcp_notes_user.html @@ -55,8 +55,25 @@ {L_SEARCH_KEYWORDS}:   @@ -102,8 +119,25 @@ diff --git a/phpBB/styles/prosilver/template/mcp_queue.html b/phpBB/styles/prosilver/template/mcp_queue.html index e9a477a24c..5dd4308f7a 100644 --- a/phpBB/styles/prosilver/template/mcp_queue.html +++ b/phpBB/styles/prosilver/template/mcp_queue.html @@ -18,8 +18,25 @@
    @@ -73,8 +90,25 @@ diff --git a/phpBB/styles/prosilver/template/mcp_reports.html b/phpBB/styles/prosilver/template/mcp_reports.html index 95e7d9e021..431f50d346 100644 --- a/phpBB/styles/prosilver/template/mcp_reports.html +++ b/phpBB/styles/prosilver/template/mcp_reports.html @@ -20,8 +20,25 @@
      @@ -74,8 +91,25 @@
      diff --git a/phpBB/styles/prosilver/template/mcp_topic.html b/phpBB/styles/prosilver/template/mcp_topic.html index 8d0294d226..2a7b749027 100644 --- a/phpBB/styles/prosilver/template/mcp_topic.html +++ b/phpBB/styles/prosilver/template/mcp_topic.html @@ -139,11 +139,28 @@ onload_functions.push('subPanels()');
      - + diff --git a/phpBB/styles/prosilver/template/mcp_warn_list.html b/phpBB/styles/prosilver/template/mcp_warn_list.html index e4f82bbe67..27adcf40e3 100644 --- a/phpBB/styles/prosilver/template/mcp_warn_list.html +++ b/phpBB/styles/prosilver/template/mcp_warn_list.html @@ -12,8 +12,25 @@ @@ -48,8 +65,25 @@ diff --git a/phpBB/styles/prosilver/template/memberlist_body.html b/phpBB/styles/prosilver/template/memberlist_body.html index d5154761e9..bb3ce814d3 100644 --- a/phpBB/styles/prosilver/template/memberlist_body.html +++ b/phpBB/styles/prosilver/template/memberlist_body.html @@ -41,8 +41,26 @@
    @@ -150,7 +168,27 @@
    diff --git a/phpBB/styles/prosilver/template/posting_smilies.html b/phpBB/styles/prosilver/template/posting_smilies.html index d3d6293586..097d08244d 100644 --- a/phpBB/styles/prosilver/template/posting_smilies.html +++ b/phpBB/styles/prosilver/template/posting_smilies.html @@ -17,7 +17,24 @@
-
{PAGINATION}
+ {L_CLOSE_WINDOW} diff --git a/phpBB/styles/prosilver/template/search_results.html b/phpBB/styles/prosilver/template/search_results.html index 0a58605b31..7b4ecaab72 100644 --- a/phpBB/styles/prosilver/template/search_results.html +++ b/phpBB/styles/prosilver/template/search_results.html @@ -11,7 +11,7 @@

{L_RETURN_TO_SEARCH_ADV}

- +
@@ -26,7 +26,26 @@
@@ -59,7 +78,20 @@ {searchresults.TOPIC_TITLE} {searchresults.ATTACH_ICON_IMG} {searchresults.UNAPPROVED_IMG} {REPORTED_IMG}
- {searchresults.PAGINATION} + + + {L_POST_BY_AUTHOR} {searchresults.TOPIC_AUTHOR_FULL} » {searchresults.FIRST_POST_TIME} » {L_IN} {searchresults.FORUM_TITLE}
{searchresults.TOPIC_REPLIES}
@@ -127,7 +159,7 @@ - +
@@ -144,10 +176,29 @@
- + diff --git a/phpBB/styles/prosilver/template/ucp_attachments.html b/phpBB/styles/prosilver/template/ucp_attachments.html index 84e4c2e875..b57376344f 100644 --- a/phpBB/styles/prosilver/template/ucp_attachments.html +++ b/phpBB/styles/prosilver/template/ucp_attachments.html @@ -12,8 +12,25 @@ @@ -55,8 +72,25 @@ diff --git a/phpBB/styles/prosilver/template/ucp_groups_manage.html b/phpBB/styles/prosilver/template/ucp_groups_manage.html index a13c043e48..680d39ea1e 100644 --- a/phpBB/styles/prosilver/template/ucp_groups_manage.html +++ b/phpBB/styles/prosilver/template/ucp_groups_manage.html @@ -159,7 +159,24 @@ diff --git a/phpBB/styles/prosilver/template/ucp_main_bookmarks.html b/phpBB/styles/prosilver/template/ucp_main_bookmarks.html index 89502bbc3d..895fce5e51 100644 --- a/phpBB/styles/prosilver/template/ucp_main_bookmarks.html +++ b/phpBB/styles/prosilver/template/ucp_main_bookmarks.html @@ -36,7 +36,20 @@ {NEWEST_POST_IMG} {topicrow.TOPIC_TITLE} {topicrow.UNAPPROVED_IMG} {REPORTED_IMG}
- {topicrow.PAGINATION} + + + {topicrow.ATTACH_ICON_IMG} {L_POST_BY_AUTHOR} {topicrow.TOPIC_AUTHOR_FULL} » {topicrow.FIRST_POST_TIME}
{L_LAST_POST} {L_POST_BY_AUTHOR} {topicrow.LAST_POST_AUTHOR_FULL} @@ -50,8 +63,25 @@ diff --git a/phpBB/styles/prosilver/template/ucp_main_front.html b/phpBB/styles/prosilver/template/ucp_main_front.html index b7a0619227..04767b6c01 100644 --- a/phpBB/styles/prosilver/template/ucp_main_front.html +++ b/phpBB/styles/prosilver/template/ucp_main_front.html @@ -16,7 +16,20 @@
style="background-image: url({T_ICONS_PATH}{topicrow.TOPIC_ICON_IMG}); background-repeat: no-repeat;"> {NEWEST_POST_IMG} {topicrow.TOPIC_TITLE}
- {topicrow.PAGINATION} + + + {topicrow.ATTACH_ICON_IMG} {L_POST_BY_AUTHOR} {topicrow.TOPIC_AUTHOR_FULL} » {topicrow.FIRST_POST_TIME}
{L_LAST_POST} {L_POST_BY_AUTHOR} {topicrow.LAST_POST_AUTHOR_FULL} diff --git a/phpBB/styles/prosilver/template/ucp_main_subscribed.html b/phpBB/styles/prosilver/template/ucp_main_subscribed.html index ab65d9b3ae..0324218149 100644 --- a/phpBB/styles/prosilver/template/ucp_main_subscribed.html +++ b/phpBB/styles/prosilver/template/ucp_main_subscribed.html @@ -56,7 +56,20 @@ {NEWEST_POST_IMG} {topicrow.TOPIC_TITLE} {topicrow.UNAPPROVED_IMG} {REPORTED_IMG}
- {topicrow.PAGINATION} + + + {topicrow.ATTACH_ICON_IMG} {L_POST_BY_AUTHOR} {topicrow.TOPIC_AUTHOR_FULL} » {topicrow.FIRST_POST_TIME}
{L_LAST_POST} {L_POST_BY_AUTHOR} {topicrow.LAST_POST_AUTHOR_FULL} @@ -69,8 +82,25 @@ diff --git a/phpBB/styles/prosilver/template/ucp_pm_message_header.html b/phpBB/styles/prosilver/template/ucp_pm_message_header.html index d6659fad0f..9af2bf07a5 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_message_header.html +++ b/phpBB/styles/prosilver/template/ucp_pm_message_header.html @@ -20,8 +20,25 @@ diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewfolder.html b/phpBB/styles/prosilver/template/ucp_pm_viewfolder.html index 20394b254e..8f969a7018 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewfolder.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewfolder.html @@ -103,8 +103,25 @@ diff --git a/phpBB/styles/prosilver/template/viewforum_body.html b/phpBB/styles/prosilver/template/viewforum_body.html index f5ff1d3f98..4e368120b8 100644 --- a/phpBB/styles/prosilver/template/viewforum_body.html +++ b/phpBB/styles/prosilver/template/viewforum_body.html @@ -34,7 +34,7 @@ - +
style="margin-top: 2em;"> @@ -55,11 +55,27 @@
- + @@ -142,7 +158,20 @@ style="background-image: url({T_ICONS_PATH}{topicrow.TOPIC_ICON_IMG}); background-repeat: no-repeat;" title="{topicrow.TOPIC_FOLDER_IMG_ALT}">{NEWEST_POST_IMG} {topicrow.TOPIC_TITLE} {topicrow.UNAPPROVED_IMG} {REPORTED_IMG}
- {topicrow.PAGINATION} + + + {topicrow.ATTACH_ICON_IMG} {L_POST_BY_AUTHOR} {topicrow.TOPIC_AUTHOR_FULL} » {topicrow.FIRST_POST_TIME} » {L_IN} {topicrow.FORUM_NAME} @@ -193,13 +222,29 @@ - + diff --git a/phpBB/styles/prosilver/template/viewonline_body.html b/phpBB/styles/prosilver/template/viewonline_body.html index 9da8202783..775f182c07 100644 --- a/phpBB/styles/prosilver/template/viewonline_body.html +++ b/phpBB/styles/prosilver/template/viewonline_body.html @@ -4,7 +4,26 @@

{TOTAL_GUEST_USERS_ONLINE}{L_SWITCH_GUEST_DISPLAY}

@@ -50,7 +69,26 @@

{L_LEGEND}: {LEGEND}

diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index 1af512732d..29786f7b86 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -45,10 +45,24 @@
- + @@ -252,10 +266,24 @@ - + diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index fe6a7a7fda..29968cbb14 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -145,25 +145,30 @@ dl.details dd { /* Pagination ---------------------------------------- */ -.pagination span strong { - color: #FFFFFF; - background-color: #4692BF; - border-color: #4692BF; -} - -.pagination span a, .pagination span a:link, .pagination span a:visited { +.pagination li a, .pagination li a:link, .pagination li a:visited { color: #5C758C; background-color: #ECEDEE; border-color: #B4BAC0; } -.pagination span a:hover { +.pagination li.ellipsis span { + background-color: transparent; + color: #000 +} + +.pagination li.active span { + color: #FFFFFF; + background-color: #4692BF; + border-color: #4692BF; +} + +.pagination li a:hover, .pagination .active a:hover { border-color: #368AD2; background-color: #368AD2; color: #FFF; } -.pagination span a:active { +.pagination li a:active, .pagination li.active a:active { color: #5C758C; background-color: #ECEDEE; border-color: #B4BAC0; diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 3dda343829..d49b54475f 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -483,6 +483,7 @@ dl.details dd { overflow: hidden; } + /* Pagination ---------------------------------------- */ .pagination { @@ -493,51 +494,43 @@ dl.details dd { float: right; } -.pagination span.page-sep { - display: none; -} - li.pagination { margin-top: 0; } -.pagination strong, .pagination b { - font-weight: normal; +.pagination img { + vertical-align: middle; } -.pagination span strong { - padding: 0 2px; - margin: 0 2px; - font-weight: normal; - border: 1px solid transparent; - font-size: 0.9em; +.pagination ul { + display: inline-block; + *display: inline; /* IE7 inline-block hack */ + *zoom: 1; + margin-left: 0; + margin-bottom: 0; } -.pagination span a, .pagination span a:link, .pagination span a:visited, .pagination span a:active { +.pagination ul li, dl .pagination ul li, dl.icon .pagination ul li { + display: inline; + padding: 0; +} + +.pagination li a, .pagnation li span, li .pagination li a, li .pagnation li span, .pagination li.active span, .pagination li.ellipsis span { font-weight: normal; text-decoration: none; - margin: 0 2px; padding: 0 2px; border: 1px solid transparent; font-size: 0.9em; line-height: 1.5em; } -.pagination span a:hover { - text-decoration: none; -} - -.pagination img { - vertical-align: middle; -} - /* Pagination in viewforum for multipage topics */ .row .pagination { display: block; float: right; width: auto; margin-top: 0; - padding: 1px 0 1px 15px; + padding: 1px 0 1px 8px; font-size: 0.9em; background: none 0 50% no-repeat; } diff --git a/phpBB/styles/prosilver/theme/content.css b/phpBB/styles/prosilver/theme/content.css index 60903911dd..81f3567aba 100644 --- a/phpBB/styles/prosilver/theme/content.css +++ b/phpBB/styles/prosilver/theme/content.css @@ -1,6 +1,8 @@ /* Content Styles ---------------------------------------- */ +/* Forum and topic lists +---------------------------------------- */ ul.topiclist { display: block; list-style-type: none; @@ -701,4 +703,4 @@ dl.pmlist dt textarea { dl.pmlist dd { margin-left: 61% !important; margin-bottom: 2px; -} +} \ No newline at end of file diff --git a/phpBB/styles/subsilver2/template/pagination.html b/phpBB/styles/subsilver2/template/pagination.html index a36eb88d8f..f78bb554fc 100644 --- a/phpBB/styles/subsilver2/template/pagination.html +++ b/phpBB/styles/subsilver2/template/pagination.html @@ -1 +1,11 @@ -{L_GOTO_PAGE} {L_PREVIOUS}  {PAGINATION}  {L_NEXT} + + {L_GOTO_PAGE} + + {pagination.PAGE_NUMBER} + {pagination.PAGE_NUMBER} + {L_ELLIPSIS} + {pagination.PAGE_NUMBER} + {pagination.PAGE_NUMBER} + + + diff --git a/phpBB/styles/subsilver2/template/posting_smilies.html b/phpBB/styles/subsilver2/template/posting_smilies.html index 691ba239b2..30a80e26ab 100644 --- a/phpBB/styles/subsilver2/template/posting_smilies.html +++ b/phpBB/styles/subsilver2/template/posting_smilies.html @@ -16,7 +16,20 @@
- +
{L_SMILIES}
{smiley.SMILEY_CODE}
{PAGINATION}
{L_CLOSE_WINDOW}
{smiley.SMILEY_CODE}
+ + {L_GOTO_PAGE} + + {pagination.PAGE_NUMBER} + {pagination.PAGE_NUMBER} + {L_ELLIPSIS} + {pagination.PAGE_NUMBER} + {pagination.PAGE_NUMBER} + + +
+ + {L_CLOSE_WINDOW}
diff --git a/phpBB/styles/subsilver2/template/search_results.html b/phpBB/styles/subsilver2/template/search_results.html index d8a1879ca7..a143a186d7 100644 --- a/phpBB/styles/subsilver2/template/search_results.html +++ b/phpBB/styles/subsilver2/template/search_results.html @@ -42,8 +42,17 @@ {REPORTED_IMG}  - -

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: {searchresults.PAGINATION} ]

+ +

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: + + + {searchresults.pagination.PAGE_NUMBER} + {L_ELLIPSIS} + + {searchresults.pagination.PAGE_NUMBER} + + + ]

{L_IN} {searchresults.FORUM_TITLE}

diff --git a/phpBB/styles/subsilver2/template/ucp_main_bookmarks.html b/phpBB/styles/subsilver2/template/ucp_main_bookmarks.html index 2fa8f6ac2e..ba19c45eab 100644 --- a/phpBB/styles/subsilver2/template/ucp_main_bookmarks.html +++ b/phpBB/styles/subsilver2/template/ucp_main_bookmarks.html @@ -43,8 +43,17 @@

{NEWEST_POST_IMG} {topicrow.ATTACH_ICON_IMG} {topicrow.TOPIC_TITLE}

{L_GLOBAL_ANNOUNCEMENT}{L_FORUM}: {topicrow.FORUM_NAME} - -

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: {topicrow.PAGINATION} ]

+ +

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: + + + {topicrow.pagination.PAGE_NUMBER} + {L_ELLIPSIS} + + {topicrow.pagination.PAGE_NUMBER} + + + ]

diff --git a/phpBB/styles/subsilver2/template/ucp_main_subscribed.html b/phpBB/styles/subsilver2/template/ucp_main_subscribed.html index 42a452549b..13d2935757 100644 --- a/phpBB/styles/subsilver2/template/ucp_main_subscribed.html +++ b/phpBB/styles/subsilver2/template/ucp_main_subscribed.html @@ -52,8 +52,17 @@

{NEWEST_POST_IMG} {topicrow.ATTACH_ICON_IMG} {topicrow.TOPIC_TITLE}

{L_GLOBAL_ANNOUNCEMENT}{L_FORUM}: {topicrow.FORUM_NAME} - -

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: {topicrow.PAGINATION} ]

+ +

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: + + + {topicrow.pagination.PAGE_NUMBER} + {L_ELLIPSIS} + + {topicrow.pagination.PAGE_NUMBER} + + + ]

diff --git a/phpBB/styles/subsilver2/template/viewforum_body.html b/phpBB/styles/subsilver2/template/viewforum_body.html index afb8426799..7f241ce874 100644 --- a/phpBB/styles/subsilver2/template/viewforum_body.html +++ b/phpBB/styles/subsilver2/template/viewforum_body.html @@ -48,8 +48,17 @@ {REPORTED_IMG}  - -

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: {topicrow.PAGINATION} ]

+ +

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: + + + {topicrow.pagination.PAGE_NUMBER} + {L_ELLIPSIS} + + {topicrow.pagination.PAGE_NUMBER} + + + ]

{topicrow.TOPIC_AUTHOR_FULL}

@@ -199,8 +208,17 @@ {REPORTED_IMG}  - -

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: {topicrow.PAGINATION} ]

+ +

[ {GOTO_PAGE_IMG}{L_GOTO_PAGE}: + + + {topicrow.pagination.PAGE_NUMBER} + {L_ELLIPSIS} + + {topicrow.pagination.PAGE_NUMBER} + + + ]

{L_IN} {topicrow.FORUM_NAME}

diff --git a/phpBB/styles/subsilver2/template/viewonline_body.html b/phpBB/styles/subsilver2/template/viewonline_body.html index 1c8734d06a..b05a9470e0 100644 --- a/phpBB/styles/subsilver2/template/viewonline_body.html +++ b/phpBB/styles/subsilver2/template/viewonline_body.html @@ -8,7 +8,7 @@ - +
{L_GOTO_PAGE} {L_PREVIOUS}  {PAGINATION}  {L_NEXT}
@@ -39,7 +39,7 @@ - +
{L_GOTO_PAGE} {L_PREVIOUS}  {PAGINATION}  {L_NEXT}
diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 6a39f10394..f1c10e347e 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -590,8 +590,9 @@ if ($s_display_active) // otherwise the number is different from the one on the forum list $total_topic_count = $topics_count - sizeof($global_announce_forums); +generate_pagination(append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id" . ((strlen($u_sort_param)) ? "&$u_sort_param" : '')), $topics_count, $config['topics_per_page'], $start); + $template->assign_vars(array( - 'PAGINATION' => generate_pagination(append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id" . ((strlen($u_sort_param)) ? "&$u_sort_param" : '')), $topics_count, $config['topics_per_page'], $start), 'PAGE_NUMBER' => on_page($topics_count, $config['topics_per_page'], $start), 'TOTAL_TOPICS' => ($s_display_active) ? false : $user->lang('VIEW_FORUM_TOPICS', (int) $total_topic_count), )); @@ -702,7 +703,6 @@ if (sizeof($topic_list)) 'LAST_POST_AUTHOR_COLOUR' => get_username_string('colour', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), 'LAST_POST_AUTHOR_FULL' => get_username_string('full', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), - 'PAGINATION' => topic_generate_pagination($replies, $view_topic_url), 'REPLIES' => $replies, 'VIEWS' => $row['topic_views'], 'TOPIC_TITLE' => censor_text($row['topic_title']), @@ -744,6 +744,8 @@ if (sizeof($topic_list)) 'S_TOPIC_TYPE_SWITCH' => ($s_type_switch == $s_type_switch_test) ? -1 : $s_type_switch_test) ); + generate_pagination($view_topic_url, $replies + 1, $config['posts_per_page'], 1, '', true, true, 'topicrow'); + $s_type_switch = ($row['topic_type'] == POST_ANNOUNCE || $row['topic_type'] == POST_GLOBAL) ? 1 : 0; if ($unread_topic) diff --git a/phpBB/viewonline.php b/phpBB/viewonline.php index 08ca7f7a04..546152f647 100644 --- a/phpBB/viewonline.php +++ b/phpBB/viewonline.php @@ -342,7 +342,7 @@ while ($row = $db->sql_fetchrow($result)) $db->sql_freeresult($result); unset($prev_id, $prev_ip); -$pagination = generate_pagination(append_sid("{$phpbb_root_path}viewonline.$phpEx", "sg=$show_guests&sk=$sort_key&sd=$sort_dir"), $counter, $config['topics_per_page'], $start); +generate_pagination(append_sid("{$phpbb_root_path}viewonline.$phpEx", "sg=$show_guests&sk=$sort_key&sd=$sort_dir"), $counter, $config['topics_per_page'], $start); $order_legend = ($config['legend_sort_groupname']) ? 'group_name' : 'group_legend'; // Grab group details for legend display diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 0d6e9afd54..98a4f123be 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -546,7 +546,7 @@ foreach($quickmod_array as $option => $qm_ary) } // If we've got a hightlight set pass it on to pagination. -$pagination = generate_pagination(append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id" . ((strlen($u_sort_param)) ? "&$u_sort_param" : '') . (($highlight_match) ? "&hilit=$highlight" : '')), $total_posts, $config['posts_per_page'], $start); +generate_pagination(append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id" . ((strlen($u_sort_param)) ? "&$u_sort_param" : '') . (($highlight_match) ? "&hilit=$highlight" : '')), $total_posts, $config['posts_per_page'], $start); // Navigation links generate_forum_nav($topic_data); @@ -598,7 +598,6 @@ $template->assign_vars(array( 'TOPIC_AUTHOR_COLOUR' => get_username_string('colour', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']), 'TOPIC_AUTHOR' => get_username_string('username', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']), - 'PAGINATION' => $pagination, 'PAGE_NUMBER' => on_page($total_posts, $config['posts_per_page'], $start), 'TOTAL_POSTS' => $user->lang('VIEW_TOPIC_POSTS', (int) $total_posts), 'U_MCP' => ($auth->acl_get('m_', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&mode=topic_view&f=$forum_id&t=$topic_id" . (($start == 0) ? '' : "&start=$start") . ((strlen($u_sort_param)) ? "&$u_sort_param" : ''), true, $user->session_id) : '', From dc71c0629e60acccd39b59538f2e7f5b09b32509 Mon Sep 17 00:00:00 2001 From: Drae Date: Thu, 5 Jul 2012 18:56:14 +0100 Subject: [PATCH 0276/1142] [feature/pagination-as-list] Various fixes and improvements Extracted common template code for prosilver as per subsilver2. Various other fixups and oversight corrections, changed name of the "new" template function and re-introduced existing version. Altered on_page to compensate for removal of some templating vars from pagination routine. PHPBB3-10968 --- phpBB/adm/style/acp_attachments.html | 17 +- phpBB/adm/style/acp_groups.html | 12 +- phpBB/adm/style/acp_icons.html | 11 +- phpBB/adm/style/acp_inactive.html | 12 +- phpBB/adm/style/acp_logs.html | 12 +- phpBB/adm/style/acp_users.html | 24 +-- phpBB/adm/style/acp_users_feedback.html | 24 +-- phpBB/adm/style/pagination.html | 12 ++ phpBB/includes/acp/acp_attachments.php | 5 +- phpBB/includes/acp/acp_groups.php | 5 +- phpBB/includes/acp/acp_icons.php | 2 +- phpBB/includes/acp/acp_inactive.php | 5 +- phpBB/includes/acp/acp_logs.php | 5 +- phpBB/includes/acp/acp_users.php | 10 +- phpBB/includes/functions.php | 199 ++++++++++++++---- phpBB/includes/mcp/mcp_forum.php | 5 +- phpBB/includes/mcp/mcp_logs.php | 5 +- phpBB/includes/mcp/mcp_notes.php | 5 +- phpBB/includes/mcp/mcp_pm_reports.php | 7 +- phpBB/includes/mcp/mcp_queue.php | 5 +- phpBB/includes/mcp/mcp_reports.php | 5 +- phpBB/includes/mcp/mcp_topic.php | 5 +- phpBB/includes/mcp/mcp_warn.php | 5 +- phpBB/includes/ucp/ucp_attachments.php | 5 +- phpBB/includes/ucp/ucp_groups.php | 5 +- phpBB/includes/ucp/ucp_main.php | 6 +- phpBB/includes/ucp/ucp_pm_viewfolder.php | 5 +- phpBB/memberlist.php | 4 +- phpBB/search.php | 6 +- .../styles/prosilver/template/mcp_forum.html | 15 +- phpBB/styles/prosilver/template/mcp_logs.html | 30 +-- .../prosilver/template/mcp_notes_user.html | 30 +-- .../styles/prosilver/template/mcp_queue.html | 30 +-- .../prosilver/template/mcp_reports.html | 30 +-- .../styles/prosilver/template/mcp_topic.html | 15 +- .../prosilver/template/mcp_warn_list.html | 15 +- .../prosilver/template/memberlist_body.html | 30 +-- .../styles/prosilver/template/pagination.html | 15 ++ .../prosilver/template/posting_smilies.html | 15 +- .../prosilver/template/search_results.html | 15 +- .../prosilver/template/ucp_attachments.html | 30 +-- .../prosilver/template/ucp_groups_manage.html | 15 +- .../template/ucp_main_bookmarks.html | 15 +- .../template/ucp_main_subscribed.html | 15 +- .../template/ucp_pm_message_header.html | 15 +- .../prosilver/template/ucp_pm_viewfolder.html | 15 +- .../prosilver/template/viewforum_body.html | 30 +-- .../prosilver/template/viewonline_body.html | 30 +-- .../prosilver/template/viewtopic_body.html | 12 +- phpBB/styles/prosilver/theme/content.css | 4 +- phpBB/viewforum.php | 7 +- phpBB/viewonline.php | 7 +- phpBB/viewtopic.php | 9 +- 53 files changed, 303 insertions(+), 569 deletions(-) create mode 100644 phpBB/adm/style/pagination.html create mode 100644 phpBB/styles/prosilver/template/pagination.html diff --git a/phpBB/adm/style/acp_attachments.html b/phpBB/adm/style/acp_attachments.html index f3d67d2bfa..bdec9eb3cb 100644 --- a/phpBB/adm/style/acp_attachments.html +++ b/phpBB/adm/style/acp_attachments.html @@ -379,20 +379,11 @@ {L_TITLE} {L_CLOSE_WINDOW} diff --git a/phpBB/styles/prosilver/template/search_results.html b/phpBB/styles/prosilver/template/search_results.html index 7b4ecaab72..063cc8c8ea 100644 --- a/phpBB/styles/prosilver/template/search_results.html +++ b/phpBB/styles/prosilver/template/search_results.html @@ -29,20 +29,7 @@ {SEARCH_MATCHES} • - {PAGE_NUMBER} • - + {PAGE_NUMBER} diff --git a/phpBB/styles/prosilver/template/ucp_attachments.html b/phpBB/styles/prosilver/template/ucp_attachments.html index b57376344f..2160d8fcf7 100644 --- a/phpBB/styles/prosilver/template/ucp_attachments.html +++ b/phpBB/styles/prosilver/template/ucp_attachments.html @@ -14,20 +14,7 @@
+
{L_REQUIREMENTS} @@ -61,34 +60,35 @@
+
{L_AUTHOR_INFORMATION} -
-
-
{md_authors.AUTHOR_NAME}
-
- -
-
-
{md_authors.AUTHOR_EMAIL}
-
- - -
-
-
{md_authors.AUTHOR_HOMEPAGE}
-
- - -
-
-
{md_authors.AUTHOR_ROLE}
-
- - -

+
+
+
+
{md_authors.AUTHOR_NAME}
+
+ +
+
+
{md_authors.AUTHOR_EMAIL}
+
+ + +
+
+
{md_authors.AUTHOR_HOMEPAGE}
+
+ + +
+
+
{md_authors.AUTHOR_ROLE}
+
+ +
diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index 0e1d5c88a8..0e825514e6 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -155,11 +155,11 @@ class acp_extensions * @param $template An instance of the template engine * @return null */ - private function list_enabled_exts(phpbb_extension_manager $phpbb_extension_manager, phpbb_template $template) + public function list_enabled_exts(phpbb_extension_manager $phpbb_extension_manager, phpbb_template $template) { foreach ($phpbb_extension_manager->all_enabled() as $name => $location) { - $md_manager = $phpbb_extension_manager->get_extension_metadata($name, $template); + $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $template); $template->assign_block_vars('enabled', array( 'EXT_NAME' => $md_manager->get_metadata('display-name'), @@ -178,11 +178,11 @@ class acp_extensions * @param $template An instance of the template engine * @return null */ - private function list_disabled_exts(phpbb_extension_manager $phpbb_extension_manager, phpbb_template $template) + public function list_disabled_exts(phpbb_extension_manager $phpbb_extension_manager, phpbb_template $template) { foreach ($phpbb_extension_manager->all_disabled() as $name => $location) { - $md_manager = $phpbb_extension_manager->get_extension_metadata($name, $template); + $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $template); $template->assign_block_vars('disabled', array( 'EXT_NAME' => $md_manager->get_metadata('display-name'), @@ -201,13 +201,13 @@ class acp_extensions * @param $template An instance of the template engine * @return null */ - function list_available_exts(phpbb_extension_manager $phpbb_extension_manager, phpbb_template $template) + public function list_available_exts(phpbb_extension_manager $phpbb_extension_manager, phpbb_template $template) { $uninstalled = array_diff_key($phpbb_extension_manager->all_available(), $phpbb_extension_manager->all_configured()); foreach ($uninstalled as $name => $location) { - $md_manager = $phpbb_extension_manager->get_extension_metadata($name, $template); + $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $template); $template->assign_block_vars('disabled', array( 'EXT_NAME' => $md_manager->get_metadata('display-name'), diff --git a/phpBB/includes/extension/manager.php b/phpBB/includes/extension/manager.php index 9dfc0a067c..9342c936f9 100644 --- a/phpBB/includes/extension/manager.php +++ b/phpBB/includes/extension/manager.php @@ -131,7 +131,7 @@ class phpbb_extension_manager * @param string $template The template manager * @return phpbb_extension_metadata_manager Instance of the metadata manager */ - public function get_extension_metadata($name, phpbb_template $template) + public function get_extension_metadata_manager($name, phpbb_template $template) { return new phpbb_extension_metadata_manager($name, $this->db, $this, $this->phpbb_root_path, $this->php_ext, $template, $this->config); } diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php index d2dc72e5fc..aa163b1190 100644 --- a/phpBB/includes/extension/metadata_manager.php +++ b/phpBB/includes/extension/metadata_manager.php @@ -28,62 +28,9 @@ class phpbb_extension_metadata_manager protected $phpbb_root_path; protected $template; protected $ext_name; - public $metadata; + protected $metadata; protected $metadata_file; - /** - * Array of validation regular expressions, see __call() - * - * @var mixed - */ - protected $validation = array( - 'name' => '#^[a-zA-Z0-9_\x7f-\xff]{2,}/[a-zA-Z0-9_\x7f-\xff]{2,}$#', - 'type' => '#^phpbb3-extension$#', - 'description' => '#.*#', - 'version' => '#.+#', - 'licence' => '#.+#', - //'homepage' => '#([\d\w-.]+?\.(a[cdefgilmnoqrstuwz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvxyz]|d[ejkmnoz]|e[ceghrst]|f[ijkmnor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eouw]|s[abcdeghijklmnortuvyz]|t[cdfghjkmnoprtvwz]|u[augkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw]|aero|arpa|biz|com|coop|edu|info|int|gov|mil|museum|name|net|org|pro)(\b|\W(? array( - 'display-name' => '#.*#', - ), - ); - - /** - * Magic method to catch validation calls - * - * @param string $name - * @param mixed $arguments - * @return int - */ - public function __call($name, $arguments) - { - // Validation Magic methods - if (strpos($name, 'validate_') === 0) - { - // Remove validate_ - $name = substr($name, 9); - - // Replace underscores with dashes (underscores are not used) - $name = str_replace('_', '-', $name); - - if (strpos($name, 'extra-') === 0) - { - // Remove extra_ - $name = substr($name, 6); - - if (isset($this->validation['extra'][$name])) - { - // Extra means it's optional, so return true if it does not exist - return (isset($this->metadata['extra'][$name])) ? preg_match($this->validation['extra'][$name], $this->metadata['extra'][$name]) : true; - } - } - else if (isset($this->validation[$name]) && isset($this->metadata[$name])) - { - return preg_match($this->validation[$name], $this->metadata[$name]); - } - } - } - /** * Creates the metadata manager * @@ -136,7 +83,7 @@ class phpbb_extension_metadata_manager case 'all': default: // Validate the metadata - if (!$this->validate_metadata_array()) + if (!$this->validate()) { return false; } @@ -145,17 +92,17 @@ class phpbb_extension_metadata_manager break; case 'name': - return ($this->validate_name()) ? $this->metadata['name'] : false; + return ($this->validate('name')) ? $this->metadata['name'] : false; break; case 'display-name': - if (isset($this->metadata['extra']['display-name']) && $this->validate_extra_display_name()) + if (isset($this->metadata['extra']['display-name'])) { return $this->metadata['extra']['display-name']; } else { - return ($this->validate_name()) ? $this->metadata['name'] : false; + return ($this->validate('name')) ? $this->metadata['name'] : false; } break; // TODO: Add remaining cases as needed @@ -216,7 +163,7 @@ class phpbb_extension_metadata_manager /** * This array handles the validation and cleaning of the array * - * @return array Contains the cleaned and validated metadata array + * @return array Contains the cleaned metadata array */ private function clean_metadata_array() { @@ -227,40 +174,44 @@ class phpbb_extension_metadata_manager } /** - * This array handles the validation of strings - * - * @return bool True if validation succeeded, False if failed - */ - public function validate_metadata_array() - { - foreach ($this->validation as $name => $regex) - { - if (is_array($regex)) + * Validate fields + * + * @param string $name ("all" for display and enable validation + * "display" for name, type, and authors + * "name", "type") + * @return Bool False if validation fails, true if valid + */ + public function validate($name = 'display') + { + // Basic fields + $fields = array( + 'name' => '#^[a-zA-Z0-9_\x7f-\xff]{2,}/[a-zA-Z0-9_\x7f-\xff]{2,}$#', + 'type' => '#^phpbb3-extension$#', + 'licence' => '#.+#', + 'version' => '#.+#', + ); + + if (isset($fields[$name])) + { + return (isset($this->metadata[$name])) ? (bool) preg_match($this->validation[$name], $this->metadata[$name]) : false; + } + + // Validate all fields + if ($name == 'all') + { + foreach ($fields as $field => $data) { - foreach ($regex as $extra_name => $extra_regex) - { - $type = 'validate_' . $name . '_' . $extra_name; - - if (!$this->$type()) - { - return false; - } - } - } - else - { - - $type = 'validate_' . $name; - - if (!$this->$type()) + if (!$this->validate($field)) { return false; } } + + return $this->validate_authors(); } - return $this->validate_authors(); - } + return true; + } /** * Validates the contents of the authors field @@ -292,19 +243,10 @@ class phpbb_extension_metadata_manager */ public function validate_enable() { - $validate = array( - 'require_phpbb', - 'require_php', - ); - - foreach ($validate as $type) + // Check for phpBB, PHP versions + if (!$this->validate_require_phpbb || !$this->validate_require_php) { - $type = 'validate_' . $type; - - if (!$this->$type()) - { - return false; - } + return false; } return true; @@ -372,10 +314,10 @@ class phpbb_extension_metadata_manager $this->template->assign_vars(array( 'MD_NAME' => htmlspecialchars($this->metadata['name']), 'MD_TYPE' => htmlspecialchars($this->metadata['type']), - 'MD_DESCRIPTION' => htmlspecialchars($this->metadata['description']), + 'MD_DESCRIPTION' => (isset($this->metadata['description'])) ? htmlspecialchars($this->metadata['description']) : '', 'MD_HOMEPAGE' => (isset($this->metadata['homepage'])) ? $this->metadata['homepage'] : '', - 'MD_VERSION' => htmlspecialchars($this->metadata['version']), - 'MD_TIME' => htmlspecialchars($this->metadata['time']), + 'MD_VERSION' => (isset($this->metadata['version'])) ? htmlspecialchars($this->metadata['version']) : '', + 'MD_TIME' => (isset($this->metadata['time'])) ? htmlspecialchars($this->metadata['time']) : '', 'MD_LICENCE' => htmlspecialchars($this->metadata['licence']), 'MD_REQUIRE_PHP' => (isset($this->metadata['require']['php'])) ? htmlspecialchars($this->metadata['require']['php']) : '', 'MD_REQUIRE_PHPBB' => (isset($this->metadata['require']['phpbb'])) ? htmlspecialchars($this->metadata['require']['phpbb']) : '', @@ -386,7 +328,7 @@ class phpbb_extension_metadata_manager { $this->template->assign_block_vars('md_authors', array( 'AUTHOR_NAME' => htmlspecialchars($author['name']), - 'AUTHOR_EMAIL' => $author['email'], + 'AUTHOR_EMAIL' => (isset($author['email'])) ? $author['email'] : '', 'AUTHOR_HOMEPAGE' => (isset($author['homepage'])) ? $author['homepage'] : '', 'AUTHOR_ROLE' => (isset($author['role'])) ? htmlspecialchars($author['role']) : '', )); From 89f4cf6a8c10f9b0875cf7f278016aff67eb38fc Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Mon, 23 Jul 2012 19:46:21 -0500 Subject: [PATCH 0733/1142] [ticket/10631] Use exceptions for errors. Build action list dynamically. PHPBB3-10631 --- phpBB/adm/style/acp_ext_list.html | 21 +++- phpBB/includes/acp/acp_extensions.php | 111 +++++++++++++----- phpBB/includes/exception/metadata.php | 69 +++++++++++ phpBB/includes/extension/metadata_manager.php | 72 ++++++------ phpBB/language/en/acp/extensions.php | 2 +- 5 files changed, 205 insertions(+), 70 deletions(-) create mode 100644 phpBB/includes/exception/metadata.php diff --git a/phpBB/adm/style/acp_ext_list.html b/phpBB/adm/style/acp_ext_list.html index b3b1b84949..65051cbae0 100644 --- a/phpBB/adm/style/acp_ext_list.html +++ b/phpBB/adm/style/acp_ext_list.html @@ -26,11 +26,16 @@ {enabled.EXT_NAME} {L_DETAILS} - {L_DISABLE}  - | {L_PURGE} + + + {enabled.actions.L_ACTION} +  |  + + + {L_DISABLED} {L_EXTENSIONS} @@ -38,9 +43,15 @@ {disabled.EXT_NAME} - {L_DETAILS} - {L_ENABLE}  - {L_PURGE}  + + {L_DETAILS} + + + + {disabled.actions.L_ACTION} +  |  + + diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index 0e825514e6..a833c8c482 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -22,11 +22,21 @@ class acp_extensions { var $u_action; + private $db; + private $config; + private $template; + private $user; + function main() { // Start the page global $config, $user, $template, $request, $phpbb_extension_manager, $db, $phpbb_root_path, $phpEx; + $this->db = $db; + $this->config = $config; + $this->template = $template; + $this->user = $user; + $user->add_lang(array('install', 'acp/extensions')); $this->page_title = 'ACP_EXTENSIONS'; @@ -39,9 +49,10 @@ class acp_extensions { $md_manager = new phpbb_extension_metadata_manager($ext_name, $db, $phpbb_extension_manager, $phpbb_root_path, ".$phpEx", $template, $config); - if ($md_manager->get_metadata('all') === false) - { - trigger_error('EXTENSION_INVALID'); + try{ + $md_manager->get_metadata('all'); + } catch( Exception $e ) { + trigger_error($e); } } @@ -50,9 +61,9 @@ class acp_extensions { case 'list': default: - $this->list_enabled_exts($phpbb_extension_manager, $template); - $this->list_disabled_exts($phpbb_extension_manager, $template); - $this->list_available_exts($phpbb_extension_manager, $template); + $this->list_enabled_exts($phpbb_extension_manager); + $this->list_disabled_exts($phpbb_extension_manager); + $this->list_available_exts($phpbb_extension_manager); $this->tpl_name = 'acp_ext_list'; break; @@ -155,19 +166,28 @@ class acp_extensions * @param $template An instance of the template engine * @return null */ - public function list_enabled_exts(phpbb_extension_manager $phpbb_extension_manager, phpbb_template $template) + public function list_enabled_exts(phpbb_extension_manager $phpbb_extension_manager) { foreach ($phpbb_extension_manager->all_enabled() as $name => $location) { - $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $template); + $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $this->template); - $template->assign_block_vars('enabled', array( - 'EXT_NAME' => $md_manager->get_metadata('display-name'), + try { + $this->template->assign_block_vars('enabled', array( + 'EXT_NAME' => $md_manager->get_metadata('display-name'), - 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . $name, - 'U_PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . $name, - 'U_DISABLE' => $this->u_action . '&action=disable_pre&ext_name=' . $name, - )); + 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . $name, + )); + + $this->output_actions('enabled', array( + 'DISABLE' => $this->u_action . '&action=disable_pre&ext_name=' . $name, + 'PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . $name, + )); + } catch( Exception $e ) { + $this->template->assign_block_vars('disabled', array( + 'EXT_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), + )); + } } } @@ -178,19 +198,28 @@ class acp_extensions * @param $template An instance of the template engine * @return null */ - public function list_disabled_exts(phpbb_extension_manager $phpbb_extension_manager, phpbb_template $template) + public function list_disabled_exts(phpbb_extension_manager $phpbb_extension_manager) { foreach ($phpbb_extension_manager->all_disabled() as $name => $location) { - $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $template); + $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $this->template); - $template->assign_block_vars('disabled', array( - 'EXT_NAME' => $md_manager->get_metadata('display-name'), + try { + $this->template->assign_block_vars('disabled', array( + 'EXT_NAME' => $md_manager->get_metadata('display-name'), - 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . $name, - 'U_PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . $name, - 'U_ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . $name, - )); + 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . $name, + )); + + $this->output_actions('disabled', array( + 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . $name, + 'PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . $name, + )); + } catch( Exception $e ) { + $this->template->assign_block_vars('disabled', array( + 'EXT_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), + )); + } } } @@ -201,19 +230,45 @@ class acp_extensions * @param $template An instance of the template engine * @return null */ - public function list_available_exts(phpbb_extension_manager $phpbb_extension_manager, phpbb_template $template) + public function list_available_exts(phpbb_extension_manager $phpbb_extension_manager) { $uninstalled = array_diff_key($phpbb_extension_manager->all_available(), $phpbb_extension_manager->all_configured()); foreach ($uninstalled as $name => $location) { - $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $template); + $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $this->template); - $template->assign_block_vars('disabled', array( - 'EXT_NAME' => $md_manager->get_metadata('display-name'), + try { + $this->template->assign_block_vars('disabled', array( + 'EXT_NAME' => $md_manager->get_metadata('display-name'), - 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . $name, - 'U_ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . $name, + 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . $name, + )); + + $this->output_actions('disabled', array( + 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . $name, + )); + } catch( Exception $e ) { + $this->template->assign_block_vars('disabled', array( + 'EXT_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), + )); + } + } + } + + /** + * Output actions to a block + * + * @param string $block + * @param array $actions + */ + private function output_actions($block, $actions) + { + foreach ($actions as $lang => $url) + { + $this->template->assign_block_vars($block . '.actions', array( + 'L_ACTION' => $this->user->lang($lang), + 'U_ACTION' => $url, )); } } diff --git a/phpBB/includes/exception/metadata.php b/phpBB/includes/exception/metadata.php new file mode 100644 index 0000000000..93cc337f55 --- /dev/null +++ b/phpBB/includes/exception/metadata.php @@ -0,0 +1,69 @@ +code = $code; + $this->field_name = $field_name; + } + + public function __toString() + { + return sprintf($this->getErrorMessage(), $this->field_name); + } + + public function getErrorMessage() + { + switch ($this->code) + { + case self::NOT_SET: + return 'The "%s" meta field has not been set.'; + break; + + case self::INVALID: + return 'The "%s" meta field is not valid.'; + break; + + case self::FILE_GET_CONTENTS: + return 'file_get_contents failed on %s'; + break; + + case self::JSON_DECODE: + return 'json_decode failed on %s'; + break; + + case self::FILE_DOES_NOT_EXIST: + return 'Required file does not exist at %s'; + break; + + default: + return 'An unexpected error has occurred.'; + break; + } + } +} \ No newline at end of file diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php index aa163b1190..126331ce1c 100644 --- a/phpBB/includes/extension/metadata_manager.php +++ b/phpBB/includes/extension/metadata_manager.php @@ -56,27 +56,18 @@ class phpbb_extension_metadata_manager * Processes and gets the metadata requested * * @param string $element All for all metadata that it has and is valid, otherwise specify which section you want by its shorthand term. - * @return bool|array Contains all of the requested metadata or bool False if not valid + * @return array Contains all of the requested metadata, throws an exception on failure */ public function get_metadata($element = 'all') { // TODO: Check ext_name exists and is an extension that exists - if (!$this->set_metadata_file()) - { - return false; - } + $this->set_metadata_file(); // Fetch the metadata - if (!$this->fetch_metadata()) - { - return false; - } + $this->fetch_metadata(); // Clean the metadata - if (!$this->clean_metadata_array()) - { - return false; - } + $this->clean_metadata_array(); switch ($element) { @@ -112,46 +103,42 @@ class phpbb_extension_metadata_manager /** * Sets the filepath of the metadata file * - * @return boolean Set to true if it exists + * @return boolean Set to true if it exists, throws an exception on failure */ private function set_metadata_file() { $ext_filepath = $this->extension_manager->get_extension_path($this->ext_name); - $metadata_filepath = $this->phpbb_root_path . $ext_filepath . '/composer.json'; + $metadata_filepath = $this->phpbb_root_path . $ext_filepath . 'composer.json'; $this->metadata_file = $metadata_filepath; if (!file_exists($this->metadata_file)) { - return false; - } - else - { - return true; + throw new phpbb_exception_metadata(phpbb_exception_metadata::FILE_DOES_NOT_EXIST, $this->metadata_file); } } /** * Gets the contents of the composer.json file * - * @return bool True of false (if loading succeeded or failed) + * @return bool True if success, throws an exception on failure */ private function fetch_metadata() { if (!file_exists($this->metadata_file)) { - return false; + throw new phpbb_exception_metadata(phpbb_exception_metadata::FILE_DOES_NOT_EXIST, $this->metadata_file); } else { if (!($file_contents = file_get_contents($this->metadata_file))) { - return false; + throw new phpbb_exception_metadata(phpbb_exception_metadata::FILE_GET_CONTENTS, $this->metadata_file); } if (($metadata = json_decode($file_contents, true)) === NULL) { - return false; + throw new phpbb_exception_metadata(phpbb_exception_metadata::JSON_DECODE, $this->metadata_file); } $this->metadata = $metadata; @@ -161,7 +148,7 @@ class phpbb_extension_metadata_manager } /** - * This array handles the validation and cleaning of the array + * This array handles the cleaning of the array * * @return array Contains the cleaned metadata array */ @@ -179,7 +166,7 @@ class phpbb_extension_metadata_manager * @param string $name ("all" for display and enable validation * "display" for name, type, and authors * "name", "type") - * @return Bool False if validation fails, true if valid + * @return Bool True if valid, throws an exception if invalid */ public function validate($name = 'display') { @@ -193,21 +180,34 @@ class phpbb_extension_metadata_manager if (isset($fields[$name])) { - return (isset($this->metadata[$name])) ? (bool) preg_match($this->validation[$name], $this->metadata[$name]) : false; + if (!isset($this->metadata[$name])) + { + throw new phpbb_exception_metadata(phpbb_exception_metadata::NOT_SET, $name); + } + + if (!preg_match($fields[$name], $this->metadata[$name])) + { + throw new phpbb_exception_metadata(phpbb_exception_metadata::INVALID, $name); + } } // Validate all fields if ($name == 'all') + { + $this->validate('display'); + + $this->validate_enable(); + } + + // Validate display fields + if ($name == 'display') { foreach ($fields as $field => $data) { - if (!$this->validate($field)) - { - return false; - } + $this->validate($field); } - return $this->validate_authors(); + $this->validate_authors(); } return true; @@ -216,20 +216,20 @@ class phpbb_extension_metadata_manager /** * Validates the contents of the authors field * - * @return boolean True when passes validation + * @return boolean True when passes validation, throws exception if invalid */ private function validate_authors() { if (empty($this->metadata['authors'])) { - return false; + throw new phpbb_exception_metadata(phpbb_exception_metadata::NOT_SET, 'authors'); } foreach ($this->metadata['authors'] as $author) { if (!isset($author['name'])) { - return false; + throw new phpbb_exception_metadata(phpbb_exception_metadata::NOT_SET, 'author name'); } } @@ -244,7 +244,7 @@ class phpbb_extension_metadata_manager public function validate_enable() { // Check for phpBB, PHP versions - if (!$this->validate_require_phpbb || !$this->validate_require_php) + if (!$this->validate_require_phpbb() || !$this->validate_require_php()) { return false; } diff --git a/phpBB/language/en/acp/extensions.php b/phpBB/language/en/acp/extensions.php index 903ec249a8..0adaff10c8 100644 --- a/phpBB/language/en/acp/extensions.php +++ b/phpBB/language/en/acp/extensions.php @@ -39,7 +39,7 @@ $lang = array_merge($lang, array( 'EXTENSIONS' => 'Extensions', 'EXTENSIONS_ADMIN' => 'Extensions Manager', 'EXTENSIONS_EXPLAIN' => 'The Extensions Manager is a tool in your phpBB Board which allows you to manage all of your extensions statuses and view information about them.', - 'EXTENSION_INVALID' => 'The selected extension is not valid.', + 'EXTENSION_INVALID_LIST' => 'The "%s" extension is not valid.

%s

', 'EXTENSION_NOT_AVAILABLE' => 'The selected extension is not available for this board, please verify your phpBB and PHP versions are allowed (see the details page).', 'DETAILS' => 'Details', From 2a7e1292919ed1397a3f1951e510d84565d002d7 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Mon, 23 Jul 2012 20:28:04 -0500 Subject: [PATCH 0734/1142] [ticket/10631] Simplify exceptions PHPBB-10631 --- phpBB/includes/acp/acp_extensions.php | 28 +++++--- phpBB/includes/exception/metadata.php | 69 ------------------- phpBB/includes/extension/exception.php | 27 ++++++++ phpBB/includes/extension/metadata_manager.php | 16 ++--- 4 files changed, 55 insertions(+), 85 deletions(-) delete mode 100644 phpBB/includes/exception/metadata.php create mode 100644 phpBB/includes/extension/exception.php diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index a833c8c482..287074395d 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -49,9 +49,12 @@ class acp_extensions { $md_manager = new phpbb_extension_metadata_manager($ext_name, $db, $phpbb_extension_manager, $phpbb_root_path, ".$phpEx", $template, $config); - try{ + try + { $md_manager->get_metadata('all'); - } catch( Exception $e ) { + } + catch(phpbb_extension_exception $e) + { trigger_error($e); } } @@ -172,7 +175,8 @@ class acp_extensions { $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $this->template); - try { + try + { $this->template->assign_block_vars('enabled', array( 'EXT_NAME' => $md_manager->get_metadata('display-name'), @@ -183,7 +187,9 @@ class acp_extensions 'DISABLE' => $this->u_action . '&action=disable_pre&ext_name=' . $name, 'PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . $name, )); - } catch( Exception $e ) { + } + catch(phpbb_extension_exception $e) + { $this->template->assign_block_vars('disabled', array( 'EXT_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), )); @@ -204,7 +210,8 @@ class acp_extensions { $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $this->template); - try { + try + { $this->template->assign_block_vars('disabled', array( 'EXT_NAME' => $md_manager->get_metadata('display-name'), @@ -215,7 +222,9 @@ class acp_extensions 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . $name, 'PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . $name, )); - } catch( Exception $e ) { + } + catch(phpbb_extension_exception $e) + { $this->template->assign_block_vars('disabled', array( 'EXT_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), )); @@ -238,7 +247,8 @@ class acp_extensions { $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $this->template); - try { + try + { $this->template->assign_block_vars('disabled', array( 'EXT_NAME' => $md_manager->get_metadata('display-name'), @@ -248,7 +258,9 @@ class acp_extensions $this->output_actions('disabled', array( 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . $name, )); - } catch( Exception $e ) { + } + catch(phpbb_extension_exception $e) + { $this->template->assign_block_vars('disabled', array( 'EXT_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), )); diff --git a/phpBB/includes/exception/metadata.php b/phpBB/includes/exception/metadata.php deleted file mode 100644 index 93cc337f55..0000000000 --- a/phpBB/includes/exception/metadata.php +++ /dev/null @@ -1,69 +0,0 @@ -code = $code; - $this->field_name = $field_name; - } - - public function __toString() - { - return sprintf($this->getErrorMessage(), $this->field_name); - } - - public function getErrorMessage() - { - switch ($this->code) - { - case self::NOT_SET: - return 'The "%s" meta field has not been set.'; - break; - - case self::INVALID: - return 'The "%s" meta field is not valid.'; - break; - - case self::FILE_GET_CONTENTS: - return 'file_get_contents failed on %s'; - break; - - case self::JSON_DECODE: - return 'json_decode failed on %s'; - break; - - case self::FILE_DOES_NOT_EXIST: - return 'Required file does not exist at %s'; - break; - - default: - return 'An unexpected error has occurred.'; - break; - } - } -} \ No newline at end of file diff --git a/phpBB/includes/extension/exception.php b/phpBB/includes/extension/exception.php new file mode 100644 index 0000000000..e08a8912ea --- /dev/null +++ b/phpBB/includes/extension/exception.php @@ -0,0 +1,27 @@ +getMessage(); + } +} \ No newline at end of file diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php index 126331ce1c..27b04d4c08 100644 --- a/phpBB/includes/extension/metadata_manager.php +++ b/phpBB/includes/extension/metadata_manager.php @@ -114,7 +114,7 @@ class phpbb_extension_metadata_manager if (!file_exists($this->metadata_file)) { - throw new phpbb_exception_metadata(phpbb_exception_metadata::FILE_DOES_NOT_EXIST, $this->metadata_file); + throw new phpbb_extension_exception('The required file does not exist: ' . $this->metadata_file); } } @@ -127,18 +127,18 @@ class phpbb_extension_metadata_manager { if (!file_exists($this->metadata_file)) { - throw new phpbb_exception_metadata(phpbb_exception_metadata::FILE_DOES_NOT_EXIST, $this->metadata_file); + throw new phpbb_extension_exception('The required file does not exist: ' . $this->metadata_file); } else { if (!($file_contents = file_get_contents($this->metadata_file))) { - throw new phpbb_exception_metadata(phpbb_exception_metadata::FILE_GET_CONTENTS, $this->metadata_file); + throw new phpbb_extension_exception('file_get_contents failed on ' . $this->metadata_file); } if (($metadata = json_decode($file_contents, true)) === NULL) { - throw new phpbb_exception_metadata(phpbb_exception_metadata::JSON_DECODE, $this->metadata_file); + throw new phpbb_extension_exception('json_decode failed on ' . $this->metadata_file); } $this->metadata = $metadata; @@ -182,12 +182,12 @@ class phpbb_extension_metadata_manager { if (!isset($this->metadata[$name])) { - throw new phpbb_exception_metadata(phpbb_exception_metadata::NOT_SET, $name); + throw new phpbb_extension_exception("Required meta field '$name' has not been set."); } if (!preg_match($fields[$name], $this->metadata[$name])) { - throw new phpbb_exception_metadata(phpbb_exception_metadata::INVALID, $name); + throw new phpbb_extension_exception("Meta field '$name' is invalid."); } } @@ -222,14 +222,14 @@ class phpbb_extension_metadata_manager { if (empty($this->metadata['authors'])) { - throw new phpbb_exception_metadata(phpbb_exception_metadata::NOT_SET, 'authors'); + throw new phpbb_extension_exception("Required meta field 'authors' has not been set."); } foreach ($this->metadata['authors'] as $author) { if (!isset($author['name'])) { - throw new phpbb_exception_metadata(phpbb_exception_metadata::NOT_SET, 'author name'); + throw new phpbb_extension_exception("Required meta field 'author name' has not been set."); } } From fd5ed30052a03d812cfdf95f4e86b0c997b5aa10 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Mon, 23 Jul 2012 20:34:47 -0500 Subject: [PATCH 0735/1142] [ticket/10631] Update tests PHPBB3-10631 --- tests/extension/manager_test.php | 2 ++ tests/test_framework/phpbb_functional_test_case.php | 1 + 2 files changed, 3 insertions(+) diff --git a/tests/extension/manager_test.php b/tests/extension/manager_test.php index 45bed247ae..df7f9f3029 100644 --- a/tests/extension/manager_test.php +++ b/tests/extension/manager_test.php @@ -27,6 +27,7 @@ class phpbb_extension_manager_test extends phpbb_database_test_case $this->extension_manager = new phpbb_extension_manager( $this->new_dbal(), + new phpbb_config(array()) 'phpbb_ext', dirname(__FILE__) . '/', '.php', @@ -90,6 +91,7 @@ class phpbb_extension_manager_test extends phpbb_database_test_case { $extension_manager = new phpbb_extension_manager( $this->new_dbal(), + new phpbb_config(array()), 'phpbb_ext', dirname(__FILE__) . '/', '.php' diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index b953017d0a..a1deeb2b63 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -125,6 +125,7 @@ class phpbb_functional_test_case extends phpbb_test_case { $this->extension_manager = new phpbb_extension_manager( $this->get_db(), + new phpbb_config(), self::$config['table_prefix'] . 'ext', $phpbb_root_path, ".$phpEx", From 747c16240fd56be0e24b4c54f01c82aa0a78b91e Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Mon, 23 Jul 2012 20:40:54 -0500 Subject: [PATCH 0736/1142] [ticket/10631] get_extension_metadata_manager -> create_extension_metadata_manager PHPBB3-10631 --- phpBB/includes/acp/acp_extensions.php | 6 +++--- phpBB/includes/extension/manager.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index 287074395d..4f59ea309b 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -173,7 +173,7 @@ class acp_extensions { foreach ($phpbb_extension_manager->all_enabled() as $name => $location) { - $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $this->template); + $md_manager = $phpbb_extension_manager->create_extension_metadata_manager($name, $this->template); try { @@ -208,7 +208,7 @@ class acp_extensions { foreach ($phpbb_extension_manager->all_disabled() as $name => $location) { - $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $this->template); + $md_manager = $phpbb_extension_manager->create_extension_metadata_manager($name, $this->template); try { @@ -245,7 +245,7 @@ class acp_extensions foreach ($uninstalled as $name => $location) { - $md_manager = $phpbb_extension_manager->get_extension_metadata_manager($name, $this->template); + $md_manager = $phpbb_extension_manager->create_extension_metadata_manager($name, $this->template); try { diff --git a/phpBB/includes/extension/manager.php b/phpBB/includes/extension/manager.php index 9342c936f9..9a518c215f 100644 --- a/phpBB/includes/extension/manager.php +++ b/phpBB/includes/extension/manager.php @@ -131,7 +131,7 @@ class phpbb_extension_manager * @param string $template The template manager * @return phpbb_extension_metadata_manager Instance of the metadata manager */ - public function get_extension_metadata_manager($name, phpbb_template $template) + public function create_extension_metadata_manager($name, phpbb_template $template) { return new phpbb_extension_metadata_manager($name, $this->db, $this, $this->phpbb_root_path, $this->php_ext, $template, $this->config); } From 1de061c4defd405da279cb1f398d7d2e4e75c573 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Tue, 24 Jul 2012 20:48:38 -0500 Subject: [PATCH 0737/1142] [ticket/10631] Fixing an error in the test script PHPBB3-10631 --- tests/extension/manager_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/extension/manager_test.php b/tests/extension/manager_test.php index df7f9f3029..5cde5bccdb 100644 --- a/tests/extension/manager_test.php +++ b/tests/extension/manager_test.php @@ -27,7 +27,7 @@ class phpbb_extension_manager_test extends phpbb_database_test_case $this->extension_manager = new phpbb_extension_manager( $this->new_dbal(), - new phpbb_config(array()) + new phpbb_config(array()), 'phpbb_ext', dirname(__FILE__) . '/', '.php', From c39f11750fa73007edd936bf600ea53ac8f95f3a Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Tue, 24 Jul 2012 21:08:11 -0500 Subject: [PATCH 0738/1142] [ticket/10631] A _start_ on a metadata manager test. No idea if it runs without errors, I do not have the testing stuff setup. PHPBB3-10631 --- .../phpbb_extension_metadata_manager_test.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/extension/phpbb_extension_metadata_manager_test.php diff --git a/tests/extension/phpbb_extension_metadata_manager_test.php b/tests/extension/phpbb_extension_metadata_manager_test.php new file mode 100644 index 0000000000..7a7f7a785d --- /dev/null +++ b/tests/extension/phpbb_extension_metadata_manager_test.php @@ -0,0 +1,55 @@ +createXMLDataSet(dirname(__FILE__) . '/fixtures/extensions.xml'); + } + + protected function setUp() + { + parent::setUp(); + + $this->extension_manager = new phpbb_extension_manager( + $this->new_dbal(), + new phpbb_config(array()), + 'phpbb_ext', + dirname(__FILE__) . '/', + '.php', + new phpbb_mock_cache + ); + } + + public function test_bar() + { + $phpbb_extension_metadata_manager = new phpbb_extension_metadata_manager( + 'bar', + $this->new_dbal(), + new phpbb_config(array()), + $this->extension_manager, + dirname(__FILE__) . '/', + '.php', + new phpbb_template( + dirname(__FILE__) . '/', + '.php', + new phpbb_config(array()), + new phpbb_user(), + new phpbb_style_resource_locator() + ), + new phpbb_mock_cache + ); + + //$this->assertEquals(array('bar', 'foo', 'vendor/moo'), array_keys($this->extension_manager->all_available())); + } +} \ No newline at end of file From 8c5786636a534baf28b4820a730f85948c3dccf4 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Wed, 25 Jul 2012 22:14:38 -0500 Subject: [PATCH 0739/1142] [ticket/10631] Fix class construct arguments in test PHPBB3-10631 --- tests/extension/phpbb_extension_metadata_manager_test.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/extension/phpbb_extension_metadata_manager_test.php b/tests/extension/phpbb_extension_metadata_manager_test.php index 7a7f7a785d..0d115d37eb 100644 --- a/tests/extension/phpbb_extension_metadata_manager_test.php +++ b/tests/extension/phpbb_extension_metadata_manager_test.php @@ -36,7 +36,6 @@ class phpbb_extension_metadata_manager_test extends phpbb_database_test_case $phpbb_extension_metadata_manager = new phpbb_extension_metadata_manager( 'bar', $this->new_dbal(), - new phpbb_config(array()), $this->extension_manager, dirname(__FILE__) . '/', '.php', @@ -47,7 +46,7 @@ class phpbb_extension_metadata_manager_test extends phpbb_database_test_case new phpbb_user(), new phpbb_style_resource_locator() ), - new phpbb_mock_cache + new phpbb_config(array()) ); //$this->assertEquals(array('bar', 'foo', 'vendor/moo'), array_keys($this->extension_manager->all_available())); From 500879520c40a71f0b83799ab3e59c86c12a801a Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Sat, 28 Jul 2012 14:59:55 -0500 Subject: [PATCH 0740/1142] [ticket/10631] Metadata manager tests PHPBB3-10631 --- phpBB/includes/extension/metadata_manager.php | 55 +-- tests/extension/ext/foo/composer.json | 22 ++ tests/extension/metadata_manager_test.php | 374 ++++++++++++++++++ .../phpbb_extension_metadata_manager_test.php | 54 --- 4 files changed, 424 insertions(+), 81 deletions(-) create mode 100644 tests/extension/ext/foo/composer.json create mode 100644 tests/extension/metadata_manager_test.php delete mode 100644 tests/extension/phpbb_extension_metadata_manager_test.php diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php index 27b04d4c08..c7f52b7c02 100644 --- a/phpBB/includes/extension/metadata_manager.php +++ b/phpBB/includes/extension/metadata_manager.php @@ -178,36 +178,37 @@ class phpbb_extension_metadata_manager 'version' => '#.+#', ); - if (isset($fields[$name])) + switch ($name) { - if (!isset($this->metadata[$name])) - { - throw new phpbb_extension_exception("Required meta field '$name' has not been set."); - } + case 'all': + $this->validate('display'); - if (!preg_match($fields[$name], $this->metadata[$name])) - { - throw new phpbb_extension_exception("Meta field '$name' is invalid."); - } - } + $this->validate_enable(); + break; - // Validate all fields - if ($name == 'all') - { - $this->validate('display'); + case 'display': + foreach ($fields as $field => $data) + { + $this->validate($field); + } - $this->validate_enable(); - } + $this->validate_authors(); + break; - // Validate display fields - if ($name == 'display') - { - foreach ($fields as $field => $data) - { - $this->validate($field); - } + default: + if (isset($fields[$name])) + { + if (!isset($this->metadata[$name])) + { + throw new phpbb_extension_exception("Required meta field '$name' has not been set."); + } - $this->validate_authors(); + if (!preg_match($fields[$name], $this->metadata[$name])) + { + throw new phpbb_extension_exception("Meta field '$name' is invalid."); + } + } + break; } return true; @@ -218,7 +219,7 @@ class phpbb_extension_metadata_manager * * @return boolean True when passes validation, throws exception if invalid */ - private function validate_authors() + public function validate_authors() { if (empty($this->metadata['authors'])) { @@ -258,7 +259,7 @@ class phpbb_extension_metadata_manager * * @return boolean True when passes validation */ - private function validate_require_phpbb() + public function validate_require_phpbb() { if (!isset($this->metadata['require']['phpbb'])) { @@ -273,7 +274,7 @@ class phpbb_extension_metadata_manager * * @return boolean True when passes validation */ - private function validate_require_php() + public function validate_require_php() { if (!isset($this->metadata['require']['php'])) { diff --git a/tests/extension/ext/foo/composer.json b/tests/extension/ext/foo/composer.json new file mode 100644 index 0000000000..14af677dac --- /dev/null +++ b/tests/extension/ext/foo/composer.json @@ -0,0 +1,22 @@ +{ + "name": "foo/example", + "type": "phpbb3-extension", + "description": "An example/sample extension to be used for testing purposes in phpBB Development.", + "version": "1.0.0", + "time": "2012-02-15 01:01:01", + "licence": "GNU GPL v2", + "authors": [{ + "name": "Nathan Guse", + "username": "EXreaction", + "email": "nathaniel.guse@gmail.com", + "homepage": "http://lithiumstudios.org", + "role": "N/A" + }], + "require": { + "php": ">=5.3", + "phpbb": "3.1.0-dev" + }, + "extra": { + "display-name": "phpBB Foo Extension" + } +} diff --git a/tests/extension/metadata_manager_test.php b/tests/extension/metadata_manager_test.php new file mode 100644 index 0000000000..67630e9f36 --- /dev/null +++ b/tests/extension/metadata_manager_test.php @@ -0,0 +1,374 @@ +createXMLDataSet(dirname(__FILE__) . '/fixtures/extensions.xml'); + } + + protected function setUp() + { + parent::setUp(); + + $this->phpbb_root_path = dirname(__FILE__) . '/'; + + $this->extension_manager = new phpbb_extension_manager( + $this->new_dbal(), + new phpbb_config(array()), + 'phpbb_ext', + $this->phpbb_root_path, + '.php', + new phpbb_mock_cache + ); + } + + // Should fail from missing composer.json + public function test_bar() + { + $ext_name = 'bar'; + + $manager = new phpbb_extension_metadata_manager_test( + $ext_name, + $this->new_dbal(), + $this->extension_manager, + $this->phpbb_root_path, + '.php', + new phpbb_template( + $this->phpbb_root_path, + '.php', + new phpbb_config(array()), + new phpbb_user(), + new phpbb_style_resource_locator() + ), + new phpbb_config(array()) + ); + + try + { + $manager->get_metadata(); + } + catch(phpbb_extension_exception $e){} + + $this->assertEquals((string) $e, 'The required file does not exist: ' . $this->phpbb_root_path . $this->extension_manager->get_extension_path($ext_name) . 'composer.json'); + } + + // Should be the same as a direct json_decode of the composer.json file + public function test_foo() + { + $ext_name = 'foo'; + + $manager = new phpbb_extension_metadata_manager_test( + $ext_name, + $this->new_dbal(), + $this->extension_manager, + $this->phpbb_root_path, + '.php', + new phpbb_template( + $this->phpbb_root_path, + '.php', + new phpbb_config(array()), + new phpbb_user(), + new phpbb_style_resource_locator() + ), + new phpbb_config(array()) + ); + + try + { + $metadata = $manager->get_metadata(); + } + catch(phpbb_extension_exception $e) + { + $this->fail($e); + } + + $json = json_decode(file_get_contents($this->phpbb_root_path . 'ext/foo/composer.json'), true); + + $this->assertEquals($metadata, $json); + } + + public function test_validator() + { + $ext_name = 'validator'; + + $manager = new phpbb_extension_metadata_manager_test( + $ext_name, + $this->new_dbal(), + $this->extension_manager, + $this->phpbb_root_path, + '.php', + new phpbb_template( + $this->phpbb_root_path, + '.php', + new phpbb_config(array()), + new phpbb_user(), + new phpbb_style_resource_locator() + ), + new phpbb_config(array( + 'version' => '3.1.0', + )) + ); + + // Non-existant data + try + { + $manager->validate('name'); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Required meta field \'name\' has not been set.'); + + try + { + $manager->validate('type'); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Required meta field \'type\' has not been set.'); + + try + { + $manager->validate('licence'); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Required meta field \'licence\' has not been set.'); + + try + { + $manager->validate('version'); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Required meta field \'version\' has not been set.'); + + try + { + $manager->validate_authors(); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Required meta field \'authors\' has not been set.'); + + $manager->merge_metadata(array( + 'authors' => array( + array(), + ), + )); + + try + { + $manager->validate_authors(); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Required meta field \'author name\' has not been set.'); + + + // Invalid data + $manager->set_metadata(array( + 'name' => 'asdf', + 'type' => 'asdf', + 'licence' => '', + 'version' => '', + )); + + try + { + $manager->validate('name'); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Meta field \'name\' is invalid.'); + + try + { + $manager->validate('type'); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Meta field \'type\' is invalid.'); + + try + { + $manager->validate('licence'); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Meta field \'licence\' is invalid.'); + + try + { + $manager->validate('version'); + } + catch(phpbb_extension_exception $e) {} + $this->assertEquals((string) $e, 'Meta field \'version\' is invalid.'); + + + // Valid data + $manager->set_metadata(array( + 'name' => 'test/foo', + 'type' => 'phpbb3-extension', + 'licence' => 'GPL v2', + 'version' => '1.0.0', + )); + + try + { + $this->assertEquals(true, $manager->validate('enable')); + } + catch(phpbb_extension_exception $e) + { + $this->fail($e); + } + + + // Too high of requirements + $manager->merge_metadata(array( + 'require' => array( + 'php' => '10.0.0', + 'phpbb' => '3.2.0', // config is set to 3.1.0 + ), + )); + + try + { + $this->assertEquals(false, $manager->validate_require_php()); + $this->assertEquals(false, $manager->validate_require_phpbb()); + } + catch(phpbb_extension_exception $e) + { + $this->fail($e); + } + + + // Too high of requirements + $manager->merge_metadata(array( + 'require' => array( + 'php' => '5.3.0', + 'phpbb' => '3.1.0-beta', // config is set to 3.1.0 + ), + )); + + try + { + $this->assertEquals(true, $manager->validate_require_php()); + $this->assertEquals(true, $manager->validate_require_phpbb()); + } + catch(phpbb_extension_exception $e) + { + $this->fail($e); + } + + + // Too high of requirements + $manager->merge_metadata(array( + 'require' => array( + 'php' => '>' . phpversion(), + 'phpbb' => '>3.1.0', // config is set to 3.1.0 + ), + )); + + try + { + $this->assertEquals(false, $manager->validate_require_php()); + $this->assertEquals(false, $manager->validate_require_phpbb()); + } + catch(phpbb_extension_exception $e) + { + $this->fail($e); + } + + + // Too high of current install + $manager->merge_metadata(array( + 'require' => array( + 'php' => '<' . phpversion(), + 'phpbb' => '<3.1.0', // config is set to 3.1.0 + ), + )); + + try + { + $this->assertEquals(false, $manager->validate_require_php()); + $this->assertEquals(false, $manager->validate_require_phpbb()); + } + catch(phpbb_extension_exception $e) + { + $this->fail($e); + } + + + // Matching requirements + $manager->merge_metadata(array( + 'require' => array( + 'php' => phpversion(), + 'phpbb' => '3.1.0', // config is set to 3.1.0 + ), + )); + + try + { + $this->assertEquals(true, $manager->validate_require_php()); + $this->assertEquals(true, $manager->validate_require_phpbb()); + } + catch(phpbb_extension_exception $e) + { + $this->fail($e); + } + + + // Matching requirements + $manager->merge_metadata(array( + 'require' => array( + 'php' => '>=' . phpversion(), + 'phpbb' => '>=3.1.0', // config is set to 3.1.0 + ), + )); + + try + { + $this->assertEquals(true, $manager->validate_require_php()); + $this->assertEquals(true, $manager->validate_require_phpbb()); + } + catch(phpbb_extension_exception $e) + { + $this->fail($e); + } + + + // Matching requirements + $manager->merge_metadata(array( + 'require' => array( + 'php' => '<=' . phpversion(), + 'phpbb' => '<=3.1.0', // config is set to 3.1.0 + ), + )); + + try + { + $this->assertEquals(true, $manager->validate_require_php()); + $this->assertEquals(true, $manager->validate_require_phpbb()); + } + catch(phpbb_extension_exception $e) + { + $this->fail($e); + } + } +} + +class phpbb_extension_metadata_manager_test extends phpbb_extension_metadata_manager +{ + public function set_metadata($metadata) + { + $this->metadata = $metadata; + } + + public function merge_metadata($metadata) + { + $this->metadata = array_merge($this->metadata, $metadata); + } +} \ No newline at end of file diff --git a/tests/extension/phpbb_extension_metadata_manager_test.php b/tests/extension/phpbb_extension_metadata_manager_test.php deleted file mode 100644 index 0d115d37eb..0000000000 --- a/tests/extension/phpbb_extension_metadata_manager_test.php +++ /dev/null @@ -1,54 +0,0 @@ -createXMLDataSet(dirname(__FILE__) . '/fixtures/extensions.xml'); - } - - protected function setUp() - { - parent::setUp(); - - $this->extension_manager = new phpbb_extension_manager( - $this->new_dbal(), - new phpbb_config(array()), - 'phpbb_ext', - dirname(__FILE__) . '/', - '.php', - new phpbb_mock_cache - ); - } - - public function test_bar() - { - $phpbb_extension_metadata_manager = new phpbb_extension_metadata_manager( - 'bar', - $this->new_dbal(), - $this->extension_manager, - dirname(__FILE__) . '/', - '.php', - new phpbb_template( - dirname(__FILE__) . '/', - '.php', - new phpbb_config(array()), - new phpbb_user(), - new phpbb_style_resource_locator() - ), - new phpbb_config(array()) - ); - - //$this->assertEquals(array('bar', 'foo', 'vendor/moo'), array_keys($this->extension_manager->all_available())); - } -} \ No newline at end of file From 36465c9a205c356b0662e45b4fded79c4b476547 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Sun, 29 Jul 2012 20:08:30 -0500 Subject: [PATCH 0741/1142] [ticket/10631] Functional acp_extensions test, cleanup PHPBB3-10631 --- phpBB/adm/style/acp_ext_list.html | 4 +- phpBB/includes/acp/acp_extensions.php | 38 +-- tests/extension/acp.php | 226 ++++++++++++++++++ tests/extension/ext/foo/composer.json | 2 +- tests/extension/ext/vendor/moo/composer.json | 22 ++ tests/extension/metadata_manager_test.php | 99 ++++---- .../phpbb_functional_test_case.php | 54 +++++ .../phpbb_test_case_helpers.php | 108 +++++++++ 8 files changed, 481 insertions(+), 72 deletions(-) create mode 100644 tests/extension/acp.php create mode 100644 tests/extension/ext/vendor/moo/composer.json diff --git a/phpBB/adm/style/acp_ext_list.html b/phpBB/adm/style/acp_ext_list.html index 65051cbae0..b654a80caa 100644 --- a/phpBB/adm/style/acp_ext_list.html +++ b/phpBB/adm/style/acp_ext_list.html @@ -23,7 +23,7 @@ - + {enabled.EXT_NAME} {L_DETAILS} @@ -41,7 +41,7 @@ {L_DISABLED} {L_EXTENSIONS} - + {disabled.EXT_NAME} {L_DETAILS} diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index 4f59ea309b..c4d9f0c0e0 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -72,7 +72,7 @@ class acp_extensions break; case 'enable_pre': - if (!$md_manager->validate_enable()) + if (!$md_manager->validate_enable() || $phpbb_extension_manager->enabled($ext_name)) { trigger_error('EXTENSION_NOT_AVAILABLE'); } @@ -81,7 +81,7 @@ class acp_extensions $template->assign_vars(array( 'PRE' => true, - 'U_ENABLE' => $this->u_action . '&action=enable&ext_name=' . $ext_name, + 'U_ENABLE' => $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name), )); break; @@ -95,7 +95,7 @@ class acp_extensions { $template->assign_var('S_NEXT_STEP', true); - meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . $ext_name); + meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name)); } $this->tpl_name = 'acp_ext_enable'; @@ -106,11 +106,16 @@ class acp_extensions break; case 'disable_pre': + if (!$phpbb_extension_manager->enabled($ext_name)) + { + trigger_error('EXTENSION_NOT_AVAILABLE'); + } + $this->tpl_name = 'acp_ext_disable'; $template->assign_vars(array( 'PRE' => true, - 'U_DISABLE' => $this->u_action . '&action=disable&ext_name=' . $ext_name, + 'U_DISABLE' => $this->u_action . '&action=disable&ext_name=' . urlencode($ext_name), )); break; @@ -119,7 +124,7 @@ class acp_extensions { $template->assign_var('S_NEXT_STEP', true); - meta_refresh(0, $this->u_action . '&action=disable&ext_name=' . $ext_name); + meta_refresh(0, $this->u_action . '&action=disable&ext_name=' . urlencode($ext_name)); } $this->tpl_name = 'acp_ext_disable'; @@ -134,7 +139,7 @@ class acp_extensions $template->assign_vars(array( 'PRE' => true, - 'U_PURGE' => $this->u_action . '&action=purge&ext_name=' . $ext_name, + 'U_PURGE' => $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name), )); break; @@ -143,7 +148,7 @@ class acp_extensions { $template->assign_var('S_NEXT_STEP', true); - meta_refresh(0, $this->u_action . '&action=purge&ext_name=' . $ext_name); + meta_refresh(0, $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name)); } $this->tpl_name = 'acp_ext_purge'; @@ -166,7 +171,6 @@ class acp_extensions * Lists all the enabled extensions and dumps to the template * * @param $phpbb_extension_manager An instance of the extension manager - * @param $template An instance of the template engine * @return null */ public function list_enabled_exts(phpbb_extension_manager $phpbb_extension_manager) @@ -180,12 +184,12 @@ class acp_extensions $this->template->assign_block_vars('enabled', array( 'EXT_NAME' => $md_manager->get_metadata('display-name'), - 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . $name, + 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . urlencode($name), )); $this->output_actions('enabled', array( - 'DISABLE' => $this->u_action . '&action=disable_pre&ext_name=' . $name, - 'PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . $name, + 'DISABLE' => $this->u_action . '&action=disable_pre&ext_name=' . urlencode($name), + 'PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . urlencode($name), )); } catch(phpbb_extension_exception $e) @@ -201,7 +205,6 @@ class acp_extensions * Lists all the disabled extensions and dumps to the template * * @param $phpbb_extension_manager An instance of the extension manager - * @param $template An instance of the template engine * @return null */ public function list_disabled_exts(phpbb_extension_manager $phpbb_extension_manager) @@ -215,12 +218,12 @@ class acp_extensions $this->template->assign_block_vars('disabled', array( 'EXT_NAME' => $md_manager->get_metadata('display-name'), - 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . $name, + 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . urlencode($name), )); $this->output_actions('disabled', array( - 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . $name, - 'PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . $name, + 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . urlencode($name), + 'PURGE' => $this->u_action . '&action=purge_pre&ext_name=' . urlencode($name), )); } catch(phpbb_extension_exception $e) @@ -236,7 +239,6 @@ class acp_extensions * Lists all the available extensions and dumps to the template * * @param $phpbb_extension_manager An instance of the extension manager - * @param $template An instance of the template engine * @return null */ public function list_available_exts(phpbb_extension_manager $phpbb_extension_manager) @@ -252,11 +254,11 @@ class acp_extensions $this->template->assign_block_vars('disabled', array( 'EXT_NAME' => $md_manager->get_metadata('display-name'), - 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . $name, + 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . urlencode($name), )); $this->output_actions('disabled', array( - 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . $name, + 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . urlencode($name), )); } catch(phpbb_extension_exception $e) diff --git a/tests/extension/acp.php b/tests/extension/acp.php new file mode 100644 index 0000000000..c078a3f7b4 --- /dev/null +++ b/tests/extension/acp.php @@ -0,0 +1,226 @@ +copy_dir($phpbb_root_path . 'ext/', $phpbb_root_path . 'store/temp_ext/'); + + // Then empty the ext/ directory on the board (for accurate test cases) + self::$helper->empty_dir($phpbb_root_path . 'ext/'); + + // Copy our ext/ files from the test case to the board + self::$copied_files = array_merge(self::$copied_files, self::$helper->copy_dir(dirname(__FILE__) . '/ext/', $phpbb_root_path . 'ext/')); + } + + public function setUp() + { + parent::setUp(); + + $this->get_db(); + + // Clear the phpbb_ext table + $this->db->sql_query('DELETE FROM phpbb_ext'); + + // Insert our base data + $insert_rows = array( + array( + 'ext_name' => 'foo', + 'ext_active' => true, + 'ext_state' => 'b:0;', + ), + array( + 'ext_name' => 'vendor/moo', + 'ext_active' => false, + 'ext_state' => 'b:0;', + ), + + // do not exist + array( + 'ext_name' => 'test2', + 'ext_active' => true, + 'ext_state' => 'b:0;', + ), + array( + 'ext_name' => 'test3', + 'ext_active' => false, + 'ext_state' => 'b:0;', + ), + ); + $this->db->sql_multi_insert('phpbb_ext', $insert_rows); + + $this->login(); + $this->admin_login(); + + $this->add_lang('acp/extensions'); + } + + /** + * This should only be called once after the tests are run. + * This is used to remove the files copied to the phpBB install + */ + static public function tearDownAfterClass() + { + global $phpbb_root_path; + + // Copy back the board installed extensions from the temp directory + self::$helper->copy_dir($phpbb_root_path . 'store/temp_ext/', $phpbb_root_path . 'ext/'); + + self::$copied_files[] = $phpbb_root_path . 'store/temp_ext/'; + + // Remove all of the files we copied around (from board ext -> temp_ext, from test ext -> board ext) + self::$helper->remove_files(self::$copied_files); + } + + public function test_list() + { + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); + + $this->assertCount(1, $crawler->filter('.ext_enabled')); + $this->assertCount(4, $crawler->filter('.ext_disabled')); + + $this->assertContains('phpBB Foo Extension', $crawler->filter('.ext_enabled')->eq(0)->text()); + $this->assertContainsLang('PURGE', $crawler->filter('.ext_enabled')->eq(0)->text()); + + $this->assertContains('The "test2" extension is not valid.', $crawler->filter('.ext_disabled')->eq(0)->text()); + + $this->assertContains('The "test3" extension is not valid.', $crawler->filter('.ext_disabled')->eq(1)->text()); + + $this->assertContains('phpBB Moo Extension', $crawler->filter('.ext_disabled')->eq(2)->text()); + $this->assertContainsLang('DETAILS', $crawler->filter('.ext_disabled')->eq(2)->text()); + $this->assertContainsLang('ENABLE', $crawler->filter('.ext_disabled')->eq(2)->text()); + $this->assertContainsLang('PURGE', $crawler->filter('.ext_disabled')->eq(2)->text()); + + $this->assertContains('The "bar" extension is not valid.', $crawler->filter('.ext_disabled')->eq(3)->text()); + } + + public function test_details() + { + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=details&ext_name=foo&sid=' . $this->sid); + + for ($i = 0; $i < $crawler->filter('dl')->count(); $i++) + { + $text = $crawler->filter('dl')->eq($i)->text(); + + switch (true) + { + case (strpos($text, $this->lang('DISPLAY_NAME')) === 0): + $this->assertContains('phpBB Foo Extension', $text); + break; + + case (strpos($text, $this->lang('CLEAN_NAME')) === 0): + $this->assertContains('foo/example', $text); + break; + + case (strpos($text, $this->lang('DESCRIPTION')) === 0): + $this->assertContains('An example/sample extension to be used for testing purposes in phpBB Development.', $text); + break; + + case (strpos($text, $this->lang('VERSION')) === 0): + $this->assertContains('1.0.0', $text); + break; + + case (strpos($text, $this->lang('TIME')) === 0): + $this->assertContains('2012-02-15 01:01:01', $text); + break; + + case (strpos($text, $this->lang('LICENCE')) === 0): + $this->assertContains('GNU GPL v2', $text); + break; + + case (strpos($text, $this->lang('PHPBB_VERSION')) === 0): + $this->assertContains('3.1.0-dev', $text); + break; + + case (strpos($text, $this->lang('PHP_VERSION')) === 0): + $this->assertContains('>=5.3', $text); + break; + + case (strpos($text, $this->lang('AUTHOR_NAME')) === 0): + $this->assertContains('Nathan Guse', $text); + break; + + case (strpos($text, $this->lang('AUTHOR_EMAIL')) === 0): + $this->assertContains('email@phpbb.com', $text); + break; + + case (strpos($text, $this->lang('AUTHOR_HOMEPAGE')) === 0): + $this->assertContains('http://lithiumstudios.org', $text); + break; + + case (strpos($text, $this->lang('AUTHOR_ROLE')) === 0): + $this->assertContains('N/A', $text); + break; + } + } + } + + public function test_enable_pre() + { + // Foo is already enabled (error) + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable_pre&ext_name=foo&sid=' . $this->sid); + $this->assertContainsLang('EXTENSION_NOT_AVAILABLE', $crawler->filter('html')->text()); + + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable_pre&ext_name=vendor%2Fmoo&sid=' . $this->sid); + $this->assertContainsLang('ENABLE_CONFIRM', $crawler->filter('html')->text()); + } + + public function test_disable_pre() + { + // Moo is not enabled (error) + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable_pre&ext_name=vendor%2Fmoo&sid=' . $this->sid); + $this->assertContainsLang('EXTENSION_NOT_AVAILABLE', $crawler->filter('html')->text()); + + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable_pre&ext_name=foo&sid=' . $this->sid); + $this->assertContainsLang('DISABLE_CONFIRM', $crawler->filter('html')->text()); + } + + public function test_purge_pre() + { + // test2 is not available (error) + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge_pre&ext_name=test2&sid=' . $this->sid); + $this->assertContains('The required file does not exist', $crawler->filter('html')->text()); + + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge_pre&ext_name=foo&sid=' . $this->sid); + $this->assertContainsLang('PURGE_CONFIRM', $crawler->filter('html')->text()); + } + + public function test_enable() + { + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable&ext_name=vendor%2Fmoo&sid=' . $this->sid); + $this->assertContainsLang('ENABLE_SUCCESS', $crawler->filter('html')->text()); + } + + public function test_disable() + { + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable&ext_name=vendor%2Fmoo&sid=' . $this->sid); + $this->assertContainsLang('DISABLE_SUCCESS', $crawler->filter('html')->text()); + } + + public function test_purge() + { + $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge&ext_name=vendor%2Fmoo&sid=' . $this->sid); + $this->assertContainsLang('PURGE_SUCCESS', $crawler->filter('html')->text()); + } +} \ No newline at end of file diff --git a/tests/extension/ext/foo/composer.json b/tests/extension/ext/foo/composer.json index 14af677dac..4b5150461f 100644 --- a/tests/extension/ext/foo/composer.json +++ b/tests/extension/ext/foo/composer.json @@ -8,7 +8,7 @@ "authors": [{ "name": "Nathan Guse", "username": "EXreaction", - "email": "nathaniel.guse@gmail.com", + "email": "email@phpbb.com", "homepage": "http://lithiumstudios.org", "role": "N/A" }], diff --git a/tests/extension/ext/vendor/moo/composer.json b/tests/extension/ext/vendor/moo/composer.json new file mode 100644 index 0000000000..c91a5e027b --- /dev/null +++ b/tests/extension/ext/vendor/moo/composer.json @@ -0,0 +1,22 @@ +{ + "name": "moo/example", + "type": "phpbb3-extension", + "description": "An example/sample extension to be used for testing purposes in phpBB Development.", + "version": "1.0.0", + "time": "2012-02-15 01:01:01", + "licence": "GNU GPL v2", + "authors": [{ + "name": "Nathan Guse", + "username": "EXreaction", + "email": "email@phpbb.com", + "homepage": "http://lithiumstudios.org", + "role": "N/A" + }], + "require": { + "php": ">=5.3", + "phpbb": "3.1.0-dev" + }, + "extra": { + "display-name": "phpBB Moo Extension" + } +} diff --git a/tests/extension/metadata_manager_test.php b/tests/extension/metadata_manager_test.php index 67630e9f36..d1e60ad268 100644 --- a/tests/extension/metadata_manager_test.php +++ b/tests/extension/metadata_manager_test.php @@ -11,7 +11,14 @@ class metadata_manager_test extends phpbb_database_test_case { protected $class_loader; protected $extension_manager; + + protected $cache; + protected $config; + protected $db; protected $phpbb_root_path; + protected $phpEx; + protected $template; + protected $user; public function getDataSet() { @@ -22,15 +29,30 @@ class metadata_manager_test extends phpbb_database_test_case { parent::setUp(); + $this->cache = new phpbb_mock_cache(); + $this->config = new phpbb_config(array( + 'version' => '3.1.0', + )); + $this->db = $this->new_dbal(); $this->phpbb_root_path = dirname(__FILE__) . '/'; + $this->phpEx = '.php'; + $this->user = new phpbb_user(); + + $this->template = new phpbb_template( + $this->phpbb_root_path, + $this->phpEx, + $this->config, + $this->user, + new phpbb_style_resource_locator() + ); $this->extension_manager = new phpbb_extension_manager( - $this->new_dbal(), - new phpbb_config(array()), + $this->db(), + $this->config, 'phpbb_ext', $this->phpbb_root_path, - '.php', - new phpbb_mock_cache + $this->phpEx, + $this->cache ); } @@ -39,21 +61,7 @@ class metadata_manager_test extends phpbb_database_test_case { $ext_name = 'bar'; - $manager = new phpbb_extension_metadata_manager_test( - $ext_name, - $this->new_dbal(), - $this->extension_manager, - $this->phpbb_root_path, - '.php', - new phpbb_template( - $this->phpbb_root_path, - '.php', - new phpbb_config(array()), - new phpbb_user(), - new phpbb_style_resource_locator() - ), - new phpbb_config(array()) - ); + $manager = $this->get_metadata_manager($ext_name); try { @@ -69,21 +77,7 @@ class metadata_manager_test extends phpbb_database_test_case { $ext_name = 'foo'; - $manager = new phpbb_extension_metadata_manager_test( - $ext_name, - $this->new_dbal(), - $this->extension_manager, - $this->phpbb_root_path, - '.php', - new phpbb_template( - $this->phpbb_root_path, - '.php', - new phpbb_config(array()), - new phpbb_user(), - new phpbb_style_resource_locator() - ), - new phpbb_config(array()) - ); + $manager = $this->get_metadata_manager($ext_name); try { @@ -103,23 +97,7 @@ class metadata_manager_test extends phpbb_database_test_case { $ext_name = 'validator'; - $manager = new phpbb_extension_metadata_manager_test( - $ext_name, - $this->new_dbal(), - $this->extension_manager, - $this->phpbb_root_path, - '.php', - new phpbb_template( - $this->phpbb_root_path, - '.php', - new phpbb_config(array()), - new phpbb_user(), - new phpbb_style_resource_locator() - ), - new phpbb_config(array( - 'version' => '3.1.0', - )) - ); + $manager = $this->get_metadata_manager($ext_name); // Non-existant data try @@ -358,6 +336,25 @@ class metadata_manager_test extends phpbb_database_test_case $this->fail($e); } } + + /** + * Get an instance of the metadata manager + * + * @param string $ext_name + * @return phpbb_extension_metadata_manager_test + */ + private function get_metadata_manager($ext_name) + { + return new phpbb_extension_metadata_manager_test( + $ext_name, + $this->new_dbal(), + $this->extension_manager, + $this->phpbb_root_path, + $this->phpEx, + $this->template, + $this->config + ); + } } class phpbb_extension_metadata_manager_test extends phpbb_extension_metadata_manager diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index a1deeb2b63..6b4c0b6883 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -252,6 +252,48 @@ class phpbb_functional_test_case extends phpbb_test_case } } + /** + * Login to the ACP + * You must run login() before calling this. + */ + protected function admin_login() + { + $this->add_lang('acp/common'); + + // Requires login first! + if (empty($this->sid)) + { + $this->fail('$this->sid is empty. Make sure you call login() before admin_login()'); + return; + } + + $crawler = $this->request('GET', 'adm/index.php?sid=' . $this->sid); + $this->assertContains($this->lang('LOGIN_ADMIN_CONFIRM'), $crawler->filter('html')->text()); + + $form = $crawler->selectButton($this->lang('LOGIN'))->form(); + + foreach ($form->getValues() as $field => $value) + { + if (strpos($field, 'password_') === 0) + { + $login = $this->client->submit($form, array('username' => 'admin', $field => 'admin')); + + $cookies = $this->cookieJar->all(); + + // The session id is stored in a cookie that ends with _sid - we assume there is only one such cookie + foreach ($cookies as $cookie); + { + if (substr($cookie->getName(), -4) == '_sid') + { + $this->sid = $cookie->getValue(); + } + } + + break; + } + } + } + protected function add_lang($lang_file) { if (is_array($lang_file)) @@ -288,4 +330,16 @@ class phpbb_functional_test_case extends phpbb_test_case return call_user_func_array('sprintf', $args); } + + /** + * assertContains for language strings + * + * @param string $needle Search string + * @param string $haystack Search this + * @param string $message Optional failure message + */ + public function assertContainsLang($needle, $haystack, $message = null) + { + $this->assertContains(html_entity_decode($this->lang($needle), ENT_QUOTES), $haystack, $message); + } } diff --git a/tests/test_framework/phpbb_test_case_helpers.php b/tests/test_framework/phpbb_test_case_helpers.php index 46feef550a..d10645a732 100644 --- a/tests/test_framework/phpbb_test_case_helpers.php +++ b/tests/test_framework/phpbb_test_case_helpers.php @@ -115,4 +115,112 @@ class phpbb_test_case_helpers return $config; } + + /** + * Recursive directory copying function + * + * @param string $source + * @param string $dest + * @return array list of files copied + */ + public function copy_dir($source, $dest) + { + $source = (substr($source, -1) == '/') ? $source : $source . '/'; + $dest = (substr($dest, -1) == '/') ? $dest : $dest . '/'; + + $copied_files = array(); + + if (!is_dir($dest)) + { + $this->makedirs($dest); + } + + $files = scandir($source); + foreach ($files as $file) + { + if ($file == '.' || $file == '..') + { + continue; + } + + if (is_dir($source . $file)) + { + $created_dir = false; + if (!is_dir($dest . $file)) + { + $created_dir = true; + $this->makedirs($dest . $file); + } + + $copied_files = array_merge($copied_files, self::copy_dir($source . $file, $dest . $file)); + + if ($created_dir) + { + $copied_files[] = $dest . $file; + } + } + else + { + if (!file_exists($dest . $file)) + { + copy($source . $file, $dest . $file); + + $copied_files[] = $dest . $file; + } + } + } + + return $copied_files; + } + + /** + * Remove files/directories that are listed in an array + * Designed for use with $this->copy_dir() + * + * @param array $file_list + */ + public function remove_files($file_list) + { + foreach ($file_list as $file) + { + if (is_dir($file)) + { + rmdir($file); + } + else + { + unlink($file); + } + } + } + + /** + * Empty directory (remove any subdirectories/files below) + * + * @param array $file_list + */ + public function empty_dir($path) + { + $path = (substr($path, -1) == '/') ? $path : $path . '/'; + + $files = scandir($path); + foreach ($files as $file) + { + if ($file == '.' || $file == '..') + { + continue; + } + + if (is_dir($path . $file)) + { + $this->empty_dir($path . $file); + + rmdir($path . $file); + } + else + { + unlink($path . $file); + } + } + } } From 47898cb37a1c1f517b5e6ae95427807ea5de11da Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Mon, 30 Jul 2012 13:36:51 -0500 Subject: [PATCH 0742/1142] [ticket/10631] Fix metadata_manager_test PHPBB3-10631 --- tests/extension/metadata_manager_test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/extension/metadata_manager_test.php b/tests/extension/metadata_manager_test.php index d1e60ad268..801d4dbeca 100644 --- a/tests/extension/metadata_manager_test.php +++ b/tests/extension/metadata_manager_test.php @@ -47,7 +47,7 @@ class metadata_manager_test extends phpbb_database_test_case ); $this->extension_manager = new phpbb_extension_manager( - $this->db(), + $this->db, $this->config, 'phpbb_ext', $this->phpbb_root_path, @@ -347,7 +347,7 @@ class metadata_manager_test extends phpbb_database_test_case { return new phpbb_extension_metadata_manager_test( $ext_name, - $this->new_dbal(), + $this->db, $this->extension_manager, $this->phpbb_root_path, $this->phpEx, From dce04b2d03f93c9237b743afbcbd89fb6405f836 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Mon, 30 Jul 2012 19:30:49 -0500 Subject: [PATCH 0743/1142] [ticket/10631] Various front-end fixes (extensions manager) Add Back button from details Add cancel button from actions Correct language strings PHPBB3-10631 --- phpBB/adm/style/acp_ext_details.html | 2 ++ phpBB/adm/style/acp_ext_disable.html | 5 +++-- phpBB/adm/style/acp_ext_enable.html | 3 ++- phpBB/adm/style/acp_ext_purge.html | 5 +++-- phpBB/includes/acp/acp_extensions.php | 9 +++++++++ 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/phpBB/adm/style/acp_ext_details.html b/phpBB/adm/style/acp_ext_details.html index 7408e88758..e927e9de18 100644 --- a/phpBB/adm/style/acp_ext_details.html +++ b/phpBB/adm/style/acp_ext_details.html @@ -2,6 +2,8 @@ + « {L_BACK} +

{L_EXTENSIONS_ADMIN}

diff --git a/phpBB/adm/style/acp_ext_disable.html b/phpBB/adm/style/acp_ext_disable.html index 1f0bbe14c8..7dc3f6ec97 100644 --- a/phpBB/adm/style/acp_ext_disable.html +++ b/phpBB/adm/style/acp_ext_disable.html @@ -5,7 +5,7 @@

{L_EXTENSIONS_ADMIN}

{L_EXTENSIONS_EXPLAIN}

-

{L_ENABLE_EXPLAIN}

+

{L_DISABLE_EXPLAIN}

@@ -15,7 +15,8 @@
{L_DISABLE} - + +
diff --git a/phpBB/adm/style/acp_ext_enable.html b/phpBB/adm/style/acp_ext_enable.html index 9f278bfbe0..3f7be2c847 100644 --- a/phpBB/adm/style/acp_ext_enable.html +++ b/phpBB/adm/style/acp_ext_enable.html @@ -15,7 +15,8 @@
{L_ENABLE} - + +
diff --git a/phpBB/adm/style/acp_ext_purge.html b/phpBB/adm/style/acp_ext_purge.html index 816fd872b9..00a58721cb 100644 --- a/phpBB/adm/style/acp_ext_purge.html +++ b/phpBB/adm/style/acp_ext_purge.html @@ -5,7 +5,7 @@

{L_EXTENSIONS_ADMIN}

{L_EXTENSIONS_EXPLAIN}

-

{L_ENABLE_EXPLAIN}

+

{L_PURGE_EXPLAIN}

@@ -15,7 +15,8 @@
{L_PURGE} - + +
diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index c4d9f0c0e0..1a9d51505a 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -43,6 +43,13 @@ class acp_extensions $action = $request->variable('action', 'list'); $ext_name = $request->variable('ext_name', ''); + + // Cancel action + if ($request->is_set_post('cancel')) + { + $action = 'list'; + $ext_name = ''; + } // If they've specificed an extension, let's load the metadata manager and validate it. if ($ext_name) @@ -162,6 +169,8 @@ class acp_extensions // Output it to the template $md_manager->output_template_data(); + $template->assign_var('U_BACK', $this->u_action . '&action=list'); + $this->tpl_name = 'acp_ext_details'; break; } From 7b643fe8a5fd3b92bb4db9eacb27645417004709 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Sun, 5 Aug 2012 19:00:20 -0500 Subject: [PATCH 0744/1142] [ticket/10631] Make failure to meet ext enable requirements clearer Turn the blocks red on the details page if requirement is not met. Also changing a how the errors come up when trying to enable/disable an extension when they cannot be. PHPBB3-10631 --- phpBB/adm/style/acp_ext_details.html | 6 +++--- phpBB/adm/style/admin.css | 10 ++++++++++ phpBB/includes/acp/acp_extensions.php | 15 +++++++++----- phpBB/includes/extension/metadata_manager.php | 9 +++++++-- tests/extension/acp.php | 20 +++++++++---------- 5 files changed, 39 insertions(+), 21 deletions(-) diff --git a/phpBB/adm/style/acp_ext_details.html b/phpBB/adm/style/acp_ext_details.html index e927e9de18..f477b452b7 100644 --- a/phpBB/adm/style/acp_ext_details.html +++ b/phpBB/adm/style/acp_ext_details.html @@ -3,7 +3,7 @@ « {L_BACK} - +

{L_EXTENSIONS_ADMIN}

@@ -50,13 +50,13 @@
{L_REQUIREMENTS} -
+ class="requirements_not_met">

{MD_REQUIRE_PHPBB}

-
+ class="requirements_not_met">

{MD_REQUIRE_PHP}

diff --git a/phpBB/adm/style/admin.css b/phpBB/adm/style/admin.css index 08613de0dd..585707600d 100644 --- a/phpBB/adm/style/admin.css +++ b/phpBB/adm/style/admin.css @@ -1718,3 +1718,13 @@ fieldset.permissions .padding { .phpinfo td, .phpinfo th, .phpinfo h2, .phpinfo h1 { text-align: left; } + +.requirements_not_met { + padding: 5px; + background-color: #BC2A4D; +} + +.requirements_not_met dt label, .requirements_not_met dd p { + color: #FFFFFF; + font-size: 1.4em; +} \ No newline at end of file diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index 1a9d51505a..8dde6bc36c 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -43,7 +43,7 @@ class acp_extensions $action = $request->variable('action', 'list'); $ext_name = $request->variable('ext_name', ''); - + // Cancel action if ($request->is_set_post('cancel')) { @@ -79,9 +79,14 @@ class acp_extensions break; case 'enable_pre': - if (!$md_manager->validate_enable() || $phpbb_extension_manager->enabled($ext_name)) + if (!$md_manager->validate_enable()) { - trigger_error('EXTENSION_NOT_AVAILABLE'); + trigger_error($user->lang['EXTENSION_NOT_AVAILABLE'] . adm_back_link($this->u_action)); + } + + if ($phpbb_extension_manager->enabled($ext_name)) + { + redirect($this->u_action); } $this->tpl_name = 'acp_ext_enable'; @@ -95,7 +100,7 @@ class acp_extensions case 'enable': if (!$md_manager->validate_enable()) { - trigger_error('EXTENSION_NOT_AVAILABLE'); + trigger_error($user->lang['EXTENSION_NOT_AVAILABLE'] . adm_back_link($this->u_action)); } if ($phpbb_extension_manager->enable_step($ext_name)) @@ -115,7 +120,7 @@ class acp_extensions case 'disable_pre': if (!$phpbb_extension_manager->enabled($ext_name)) { - trigger_error('EXTENSION_NOT_AVAILABLE'); + redirect($this->u_action); } $this->tpl_name = 'acp_ext_disable'; diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php index c7f52b7c02..8a68e464a7 100644 --- a/phpBB/includes/extension/metadata_manager.php +++ b/phpBB/includes/extension/metadata_manager.php @@ -320,8 +320,13 @@ class phpbb_extension_metadata_manager 'MD_VERSION' => (isset($this->metadata['version'])) ? htmlspecialchars($this->metadata['version']) : '', 'MD_TIME' => (isset($this->metadata['time'])) ? htmlspecialchars($this->metadata['time']) : '', 'MD_LICENCE' => htmlspecialchars($this->metadata['licence']), - 'MD_REQUIRE_PHP' => (isset($this->metadata['require']['php'])) ? htmlspecialchars($this->metadata['require']['php']) : '', - 'MD_REQUIRE_PHPBB' => (isset($this->metadata['require']['phpbb'])) ? htmlspecialchars($this->metadata['require']['phpbb']) : '', + + 'MD_REQUIRE_PHP' => (isset($this->metadata['require']['php'])) ? htmlspecialchars($this->metadata['require']['php']) : '', + 'MD_REQUIRE_PHP_FAIL' => !$this->validate_require_php(), + + 'MD_REQUIRE_PHPBB' => (isset($this->metadata['require']['phpbb'])) ? htmlspecialchars($this->metadata['require']['phpbb']) : '', + 'MD_REQUIRE_PHPBB_FAIL' => !$this->validate_require_phpbb(), + 'MD_DISPLAY_NAME' => (isset($this->metadata['extra']['display-name'])) ? htmlspecialchars($this->metadata['extra']['display-name']) : '', )); diff --git a/tests/extension/acp.php b/tests/extension/acp.php index c078a3f7b4..78a770343b 100644 --- a/tests/extension/acp.php +++ b/tests/extension/acp.php @@ -178,9 +178,11 @@ class acp_test extends phpbb_functional_test_case public function test_enable_pre() { - // Foo is already enabled (error) + // Foo is already enabled (redirect to list) $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable_pre&ext_name=foo&sid=' . $this->sid); - $this->assertContainsLang('EXTENSION_NOT_AVAILABLE', $crawler->filter('html')->text()); + $this->assertContainsLang('EXTENSION_NAME', $crawler->filter('html')->text()); + $this->assertContainsLang('EXTENSION_OPTIONS', $crawler->filter('html')->text()); + $this->assertContainsLang('EXTENSION_ACTIONS', $crawler->filter('html')->text()); $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable_pre&ext_name=vendor%2Fmoo&sid=' . $this->sid); $this->assertContainsLang('ENABLE_CONFIRM', $crawler->filter('html')->text()); @@ -188,9 +190,11 @@ class acp_test extends phpbb_functional_test_case public function test_disable_pre() { - // Moo is not enabled (error) + // Moo is not enabled (redirect to list) $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable_pre&ext_name=vendor%2Fmoo&sid=' . $this->sid); - $this->assertContainsLang('EXTENSION_NOT_AVAILABLE', $crawler->filter('html')->text()); + $this->assertContainsLang('EXTENSION_NAME', $crawler->filter('html')->text()); + $this->assertContainsLang('EXTENSION_OPTIONS', $crawler->filter('html')->text()); + $this->assertContainsLang('EXTENSION_ACTIONS', $crawler->filter('html')->text()); $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable_pre&ext_name=foo&sid=' . $this->sid); $this->assertContainsLang('DISABLE_CONFIRM', $crawler->filter('html')->text()); @@ -206,20 +210,14 @@ class acp_test extends phpbb_functional_test_case $this->assertContainsLang('PURGE_CONFIRM', $crawler->filter('html')->text()); } - public function test_enable() + public function test_actions() { $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable&ext_name=vendor%2Fmoo&sid=' . $this->sid); $this->assertContainsLang('ENABLE_SUCCESS', $crawler->filter('html')->text()); - } - public function test_disable() - { $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable&ext_name=vendor%2Fmoo&sid=' . $this->sid); $this->assertContainsLang('DISABLE_SUCCESS', $crawler->filter('html')->text()); - } - public function test_purge() - { $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge&ext_name=vendor%2Fmoo&sid=' . $this->sid); $this->assertContainsLang('PURGE_SUCCESS', $crawler->filter('html')->text()); } From 323bbf9b523984a592dc17bf35d2bb91a435be1b Mon Sep 17 00:00:00 2001 From: Unknown Bliss Date: Fri, 17 Aug 2012 14:06:23 +0100 Subject: [PATCH 0745/1142] [ticket/10631] Adjust prefixes to be easier to understand PHPBB3-10631 --- phpBB/adm/style/acp_ext_details.html | 74 +++++++++---------- phpBB/adm/style/acp_ext_list.html | 4 +- phpBB/includes/acp/acp_extensions.php | 12 +-- phpBB/includes/extension/metadata_manager.php | 26 +++---- 4 files changed, 58 insertions(+), 58 deletions(-) diff --git a/phpBB/adm/style/acp_ext_details.html b/phpBB/adm/style/acp_ext_details.html index f477b452b7..e7532691ad 100644 --- a/phpBB/adm/style/acp_ext_details.html +++ b/phpBB/adm/style/acp_ext_details.html @@ -8,57 +8,57 @@
{L_EXT_DETAILS} - +
-
-
{MD_DISPLAY_NAME}
+
+
{META_DISPLAY_NAME}
-
-
{MD_NAME}
+
+
{META_NAME}
- +
-
-

{MD_DESCRIPTION}

+
+

{META_DESCRIPTION}

-
-

{MD_VERSION}

+
+

{META_VERSION}

- +
-
-

{MD_HOMEPAGE}

+
+

{META_HOMEPAGE}

- +
-
-

{MD_TIME}

+
+

{META_TIME}

-
-

{MD_LICENCE}

+
+

{META_LICENCE}

- +
{L_REQUIREMENTS} - - class="requirements_not_met"> + + class="requirements_not_met">
-

{MD_REQUIRE_PHPBB}

+

{META_REQUIRE_PHPBB}

- - class="requirements_not_met"> + + class="requirements_not_met">
-

{MD_REQUIRE_PHP}

+

{META_REQUIRE_PHP}

@@ -66,32 +66,32 @@
{L_AUTHOR_INFORMATION} - +
-
-
{md_authors.AUTHOR_NAME}
+
+
{meta_authors.AUTHOR_NAME}
- +
-
-
{md_authors.AUTHOR_EMAIL}
+
+
{meta_authors.AUTHOR_EMAIL}
- +
-
-
{md_authors.AUTHOR_HOMEPAGE}
+
+
{meta_authors.AUTHOR_HOMEPAGE}
- +
-
{md_authors.AUTHOR_ROLE}
+
{meta_authors.AUTHOR_ROLE}
- +
diff --git a/phpBB/adm/style/acp_ext_list.html b/phpBB/adm/style/acp_ext_list.html index b654a80caa..53de0b4d12 100644 --- a/phpBB/adm/style/acp_ext_list.html +++ b/phpBB/adm/style/acp_ext_list.html @@ -24,7 +24,7 @@ - {enabled.EXT_NAME} + {enabled.META_DISPLAY_NAME} {L_DETAILS} @@ -42,7 +42,7 @@ - {disabled.EXT_NAME} + {disabled.META_DISPLAY_NAME} {L_DETAILS} diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index 8dde6bc36c..5a537aaa42 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -196,7 +196,7 @@ class acp_extensions try { $this->template->assign_block_vars('enabled', array( - 'EXT_NAME' => $md_manager->get_metadata('display-name'), + 'META_DISPLAY_NAME' => $md_manager->get_metadata('display-name'), 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . urlencode($name), )); @@ -209,7 +209,7 @@ class acp_extensions catch(phpbb_extension_exception $e) { $this->template->assign_block_vars('disabled', array( - 'EXT_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), + 'META_DISPLAY_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), )); } } @@ -230,7 +230,7 @@ class acp_extensions try { $this->template->assign_block_vars('disabled', array( - 'EXT_NAME' => $md_manager->get_metadata('display-name'), + 'META_DISPLAY_NAME' => $md_manager->get_metadata('display-name'), 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . urlencode($name), )); @@ -243,7 +243,7 @@ class acp_extensions catch(phpbb_extension_exception $e) { $this->template->assign_block_vars('disabled', array( - 'EXT_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), + 'META_DISPLAY_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), )); } } @@ -266,7 +266,7 @@ class acp_extensions try { $this->template->assign_block_vars('disabled', array( - 'EXT_NAME' => $md_manager->get_metadata('display-name'), + 'META_DISPLAY_NAME' => $md_manager->get_metadata('display-name'), 'U_DETAILS' => $this->u_action . '&action=details&ext_name=' . urlencode($name), )); @@ -278,7 +278,7 @@ class acp_extensions catch(phpbb_extension_exception $e) { $this->template->assign_block_vars('disabled', array( - 'EXT_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), + 'META_DISPLAY_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), )); } } diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php index 8a68e464a7..1e3bbe48c9 100644 --- a/phpBB/includes/extension/metadata_manager.php +++ b/phpBB/includes/extension/metadata_manager.php @@ -313,26 +313,26 @@ class phpbb_extension_metadata_manager public function output_template_data() { $this->template->assign_vars(array( - 'MD_NAME' => htmlspecialchars($this->metadata['name']), - 'MD_TYPE' => htmlspecialchars($this->metadata['type']), - 'MD_DESCRIPTION' => (isset($this->metadata['description'])) ? htmlspecialchars($this->metadata['description']) : '', - 'MD_HOMEPAGE' => (isset($this->metadata['homepage'])) ? $this->metadata['homepage'] : '', - 'MD_VERSION' => (isset($this->metadata['version'])) ? htmlspecialchars($this->metadata['version']) : '', - 'MD_TIME' => (isset($this->metadata['time'])) ? htmlspecialchars($this->metadata['time']) : '', - 'MD_LICENCE' => htmlspecialchars($this->metadata['licence']), + 'META_NAME' => htmlspecialchars($this->metadata['name']), + 'META_TYPE' => htmlspecialchars($this->metadata['type']), + 'META_DESCRIPTION' => (isset($this->metadata['description'])) ? htmlspecialchars($this->metadata['description']) : '', + 'META_HOMEPAGE' => (isset($this->metadata['homepage'])) ? $this->metadata['homepage'] : '', + 'META_VERSION' => (isset($this->metadata['version'])) ? htmlspecialchars($this->metadata['version']) : '', + 'META_TIME' => (isset($this->metadata['time'])) ? htmlspecialchars($this->metadata['time']) : '', + 'META_LICENCE' => htmlspecialchars($this->metadata['licence']), - 'MD_REQUIRE_PHP' => (isset($this->metadata['require']['php'])) ? htmlspecialchars($this->metadata['require']['php']) : '', - 'MD_REQUIRE_PHP_FAIL' => !$this->validate_require_php(), + 'META_REQUIRE_PHP' => (isset($this->metadata['require']['php'])) ? htmlspecialchars($this->metadata['require']['php']) : '', + 'META_REQUIRE_PHP_FAIL' => !$this->validate_require_php(), - 'MD_REQUIRE_PHPBB' => (isset($this->metadata['require']['phpbb'])) ? htmlspecialchars($this->metadata['require']['phpbb']) : '', - 'MD_REQUIRE_PHPBB_FAIL' => !$this->validate_require_phpbb(), + 'META_REQUIRE_PHPBB' => (isset($this->metadata['require']['phpbb'])) ? htmlspecialchars($this->metadata['require']['phpbb']) : '', + 'META_REQUIRE_PHPBB_FAIL' => !$this->validate_require_phpbb(), - 'MD_DISPLAY_NAME' => (isset($this->metadata['extra']['display-name'])) ? htmlspecialchars($this->metadata['extra']['display-name']) : '', + 'META_DISPLAY_NAME' => (isset($this->metadata['extra']['display-name'])) ? htmlspecialchars($this->metadata['extra']['display-name']) : '', )); foreach ($this->metadata['authors'] as $author) { - $this->template->assign_block_vars('md_authors', array( + $this->template->assign_block_vars('meta_authors', array( 'AUTHOR_NAME' => htmlspecialchars($author['name']), 'AUTHOR_EMAIL' => (isset($author['email'])) ? $author['email'] : '', 'AUTHOR_HOMEPAGE' => (isset($author['homepage'])) ? $author['homepage'] : '', From f05a175e3955d1dc1d2b85b9929ca4b30340ad4a Mon Sep 17 00:00:00 2001 From: Unknown Bliss Date: Fri, 17 Aug 2012 14:38:03 +0100 Subject: [PATCH 0746/1142] [ticket/10631] Fixing a few extension admin issues PHPBB3-10631 --- phpBB/includes/extension/metadata_manager.php | 3 --- tests/extension/ext/foo/composer.json | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php index 1e3bbe48c9..8c570830fe 100644 --- a/phpBB/includes/extension/metadata_manager.php +++ b/phpBB/includes/extension/metadata_manager.php @@ -60,7 +60,6 @@ class phpbb_extension_metadata_manager */ public function get_metadata($element = 'all') { - // TODO: Check ext_name exists and is an extension that exists $this->set_metadata_file(); // Fetch the metadata @@ -339,7 +338,5 @@ class phpbb_extension_metadata_manager 'AUTHOR_ROLE' => (isset($author['role'])) ? htmlspecialchars($author['role']) : '', )); } - - return; } } diff --git a/tests/extension/ext/foo/composer.json b/tests/extension/ext/foo/composer.json index 4b5150461f..744f7be625 100644 --- a/tests/extension/ext/foo/composer.json +++ b/tests/extension/ext/foo/composer.json @@ -4,7 +4,7 @@ "description": "An example/sample extension to be used for testing purposes in phpBB Development.", "version": "1.0.0", "time": "2012-02-15 01:01:01", - "licence": "GNU GPL v2", + "licence": "GPL-2.0", "authors": [{ "name": "Nathan Guse", "username": "EXreaction", From a9b4f2a190777612762966476ac5af9ac2a78fd0 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Mon, 27 Aug 2012 17:39:32 -0500 Subject: [PATCH 0747/1142] [ticket/10631] Some cleanup of the test cases The acp test case was not actually validating things correctly. Now is does PHPBB3-10631 --- tests/extension/acp.php | 73 ++++++--------- tests/extension/metadata_manager_test.php | 105 +++++++++++++++++----- 2 files changed, 109 insertions(+), 69 deletions(-) diff --git a/tests/extension/acp.php b/tests/extension/acp.php index 78a770343b..790df77c0d 100644 --- a/tests/extension/acp.php +++ b/tests/extension/acp.php @@ -119,59 +119,40 @@ class acp_test extends phpbb_functional_test_case { $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=details&ext_name=foo&sid=' . $this->sid); + $validation = array( + 'DISPLAY_NAME' => 'phpBB Foo Extension', + 'CLEAN_NAME' => 'foo/example', + 'DESCRIPTION' => 'An example/sample extension to be used for testing purposes in phpBB Development.', + 'VERSION' => '1.0.0', + 'TIME' => '2012-02-15 01:01:01', + 'LICENCE' => 'GPL-2.0', + 'PHPBB_VERSION' => '3.1.0-dev', + 'PHP_VERSION' => '>=5.3', + 'AUTHOR_NAME' => 'Nathan Guse', + 'AUTHOR_EMAIL' => 'email@phpbb.com', + 'AUTHOR_HOMEPAGE' => 'http://lithiumstudios.org', + 'AUTHOR_ROLE' => 'N/A', + ); + for ($i = 0; $i < $crawler->filter('dl')->count(); $i++) { $text = $crawler->filter('dl')->eq($i)->text(); - switch (true) + $match = false; + + foreach ($validation as $language_key => $expected) { - case (strpos($text, $this->lang('DISPLAY_NAME')) === 0): - $this->assertContains('phpBB Foo Extension', $text); - break; + if (strpos($text, $this->lang($language_key)) === 0) + { + $match = true; - case (strpos($text, $this->lang('CLEAN_NAME')) === 0): - $this->assertContains('foo/example', $text); - break; + $this->assertContains($expected, $text); + } + } - case (strpos($text, $this->lang('DESCRIPTION')) === 0): - $this->assertContains('An example/sample extension to be used for testing purposes in phpBB Development.', $text); - break; - - case (strpos($text, $this->lang('VERSION')) === 0): - $this->assertContains('1.0.0', $text); - break; - - case (strpos($text, $this->lang('TIME')) === 0): - $this->assertContains('2012-02-15 01:01:01', $text); - break; - - case (strpos($text, $this->lang('LICENCE')) === 0): - $this->assertContains('GNU GPL v2', $text); - break; - - case (strpos($text, $this->lang('PHPBB_VERSION')) === 0): - $this->assertContains('3.1.0-dev', $text); - break; - - case (strpos($text, $this->lang('PHP_VERSION')) === 0): - $this->assertContains('>=5.3', $text); - break; - - case (strpos($text, $this->lang('AUTHOR_NAME')) === 0): - $this->assertContains('Nathan Guse', $text); - break; - - case (strpos($text, $this->lang('AUTHOR_EMAIL')) === 0): - $this->assertContains('email@phpbb.com', $text); - break; - - case (strpos($text, $this->lang('AUTHOR_HOMEPAGE')) === 0): - $this->assertContains('http://lithiumstudios.org', $text); - break; - - case (strpos($text, $this->lang('AUTHOR_ROLE')) === 0): - $this->assertContains('N/A', $text); - break; + if (!$match) + { + $this->fail('Unexpected field: "' . $text . '"'); } } } diff --git a/tests/extension/metadata_manager_test.php b/tests/extension/metadata_manager_test.php index 801d4dbeca..9865f14314 100644 --- a/tests/extension/metadata_manager_test.php +++ b/tests/extension/metadata_manager_test.php @@ -93,7 +93,7 @@ class metadata_manager_test extends phpbb_database_test_case $this->assertEquals($metadata, $json); } - public function test_validator() + public function test_validator_non_existant() { $ext_name = 'validator'; @@ -103,37 +103,57 @@ class metadata_manager_test extends phpbb_database_test_case try { $manager->validate('name'); + + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Required meta field \'name\' has not been set.'); } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Required meta field \'name\' has not been set.'); try { $manager->validate('type'); + + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Required meta field \'type\' has not been set.'); } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Required meta field \'type\' has not been set.'); try { $manager->validate('licence'); + + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Required meta field \'licence\' has not been set.'); } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Required meta field \'licence\' has not been set.'); try { $manager->validate('version'); + + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Required meta field \'version\' has not been set.'); } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Required meta field \'version\' has not been set.'); try { $manager->validate_authors(); + + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Required meta field \'authors\' has not been set.'); } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Required meta field \'authors\' has not been set.'); $manager->merge_metadata(array( 'authors' => array( @@ -144,10 +164,21 @@ class metadata_manager_test extends phpbb_database_test_case try { $manager->validate_authors(); - } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Required meta field \'author name\' has not been set.'); + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Required meta field \'author name\' has not been set.'); + } + } + + + public function test_validator_invalid() + { + $ext_name = 'validator'; + + $manager = $this->get_metadata_manager($ext_name); // Invalid data $manager->set_metadata(array( @@ -160,31 +191,53 @@ class metadata_manager_test extends phpbb_database_test_case try { $manager->validate('name'); + + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Meta field \'name\' is invalid.'); } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Meta field \'name\' is invalid.'); try { $manager->validate('type'); + + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Meta field \'type\' is invalid.'); } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Meta field \'type\' is invalid.'); try { $manager->validate('licence'); + + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Meta field \'licence\' is invalid.'); } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Meta field \'licence\' is invalid.'); try { $manager->validate('version'); - } - catch(phpbb_extension_exception $e) {} - $this->assertEquals((string) $e, 'Meta field \'version\' is invalid.'); + $this->fail('Exception not triggered'); + } + catch(phpbb_extension_exception $e) + { + $this->assertEquals((string) $e, 'Meta field \'version\' is invalid.'); + } + } + + public function test_validator_valid() + { + $ext_name = 'validator'; + + $manager = $this->get_metadata_manager($ext_name); // Valid data $manager->set_metadata(array( @@ -202,8 +255,14 @@ class metadata_manager_test extends phpbb_database_test_case { $this->fail($e); } + } + public function test_validator_requirements() + { + $ext_name = 'validator'; + + $manager = $this->get_metadata_manager($ext_name); // Too high of requirements $manager->merge_metadata(array( 'require' => array( From 7cffebbd4997f8a41a871f8ea6fe12dc0abc08c8 Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 19 Aug 2012 17:24:38 -0400 Subject: [PATCH 0748/1142] [task/functional] Added posting tests (reply and new topic) PHPBB-10758 --- tests/functional/posting_test.php | 110 ++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 tests/functional/posting_test.php diff --git a/tests/functional/posting_test.php b/tests/functional/posting_test.php new file mode 100644 index 0000000000..8d722361e0 --- /dev/null +++ b/tests/functional/posting_test.php @@ -0,0 +1,110 @@ +login(); + $this->add_lang('posting'); + + $crawler = $this->request('GET', 'posting.php?mode=post&f=2&sid=' . $this->sid); + $this->assertContains($this->lang('POST_TOPIC'), $crawler->filter('html')->text()); + + $hidden_fields = array(); + $hidden_fields[] = $crawler->filter('[type="hidden"]')->each(function ($node, $i) { + return array('name' => $node->getAttribute('name'), 'value' => $node->getAttribute('value')); + }); + + $test_message = 'This is a test topic posted by the testing framework.'; + $form_data = array( + 'subject' => 'Test Topic 1', + 'message' => $test_message, + 'post' => true, + 'f' => 2, + 'mode' => 'post', + 'sid' => $this->sid, + ); + + foreach ($hidden_fields as $fields) + { + foreach($fields as $field) + { + $form_data[$field['name']] = $field['value']; + } + } + + // Bypass time restriction that said that if the lastclick time (i.e. time when the form was opened) + // is not at least 2 seconds before submission, cancel the form + $form_data['lastclick'] = 0; + + // The add_form_key()/check_form_key() safeguards present a challenge because they require + // the timestamp created in add_form_key() to be sent as-is to check_form_key() but in check_form_key() + // it won't allow the current time to be the same as the timestamp it requires. + // As such, automated scripts like this one have to somehow bypass this without being able to change + // the timestamp. The only way I can think to do so is using sleep() + sleep(1); + + // I use a request because the form submission method does not allow you to send data that is not + // contained in one of the actual form fields that the browser sees (i.e. it ignores "hidden" inputs) + // Instead, I send it as a request with the submit button "post" set to true. + $crawler = $this->client->request('POST', 'posting.php', $form_data); + $this->assertContains($this->lang('POST_STORED'), $crawler->filter('html')->text()); + + $crawler = $this->request('GET', 'viewtopic.php?t=2&sid=' . $this->sid); + $this->assertContains($test_message, $crawler->filter('html')->text()); + } + + public function test_post_reply() + { + $this->login(); + $this->add_lang('posting'); + + $crawler = $this->request('GET', 'posting.php?mode=reply&t=2&f=2&sid=' . $this->sid); + $this->assertContains($this->lang('POST_REPLY'), $crawler->filter('html')->text()); + + $hidden_fields = array(); + $hidden_fields[] = $crawler->filter('[type="hidden"]')->each(function ($node, $i) { + return array('name' => $node->getAttribute('name'), 'value' => $node->getAttribute('value')); + }); + + $test_message = 'This is a test post posted by the testing framework.'; + $form_data = array( + 'subject' => 'Re: Test Topic 1', + 'message' => $test_message, + 'post' => true, + 't' => 2, + 'f' => 2, + 'mode' => 'reply', + 'sid' => $this->sid, + ); + + foreach ($hidden_fields as $fields) + { + foreach($fields as $field) + { + $form_data[$field['name']] = $field['value']; + } + } + + // For reasoning behind the following two commands, see the test_post_new_topic() test + $form_data['lastclick'] = 0; + sleep(1); + + // Submit the post + $crawler = $this->client->request('POST', 'posting.php', $form_data); + $this->assertContains($this->lang('POST_STORED'), $crawler->filter('html')->text()); + + $crawler = $this->request('GET', 'viewtopic.php?t=2&sid=' . $this->sid); + $this->assertContains($test_message, $crawler->filter('html')->text()); + } +} From 7dfe26dd781e7bd0438041058e2a1d95176e7836 Mon Sep 17 00:00:00 2001 From: David King Date: Sat, 1 Sep 2012 10:35:46 -0400 Subject: [PATCH 0749/1142] [task/functional] Allow tests to bypass certain restrictions with DEBUG_TEST PHPBB3-10758 --- phpBB/includes/functions.php | 2 +- phpBB/includes/functions_install.php | 5 ++++- tests/functional/posting_test.php | 10 +--------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 5914831539..8e7e84bf83 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2811,7 +2811,7 @@ function check_form_key($form_name, $timespan = false, $return_page = '', $trigg $diff = time() - $creation_time; // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)... - if ($diff && ($diff <= $timespan || $timespan === -1)) + if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1)) { $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : ''; $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid); diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php index 89dfb7cd2f..2e10db6b24 100644 --- a/phpBB/includes/functions_install.php +++ b/phpBB/includes/functions_install.php @@ -551,10 +551,12 @@ function adjust_language_keys_callback($matches) * @param string $dbms The name of the DBAL class to use * @param array $load_extensions Array of additional extensions that should be loaded * @param bool $debug If the debug constants should be enabled by default or not +* @param bool $debug_test If the DEBUG_TEST constant should be added +* NOTE: Only for use within the testing framework * * @return string The output to write to the file */ -function phpbb_create_config_file_data($data, $dbms, $load_extensions, $debug = false) +function phpbb_create_config_file_data($data, $dbms, $load_extensions, $debug = false, $debug_test = false) { $load_extensions = implode(',', $load_extensions); @@ -584,6 +586,7 @@ function phpbb_create_config_file_data($data, $dbms, $load_extensions, $debug = { $config_data .= "@define('DEBUG', true);\n"; $config_data .= "@define('DEBUG_EXTRA', true);\n"; + $config_data .= "@define('DEBUG_TEST', true);\n"; } else { diff --git a/tests/functional/posting_test.php b/tests/functional/posting_test.php index 8d722361e0..f54a3591b2 100644 --- a/tests/functional/posting_test.php +++ b/tests/functional/posting_test.php @@ -47,13 +47,6 @@ class phpbb_functional_posting_test extends phpbb_functional_test_case // is not at least 2 seconds before submission, cancel the form $form_data['lastclick'] = 0; - // The add_form_key()/check_form_key() safeguards present a challenge because they require - // the timestamp created in add_form_key() to be sent as-is to check_form_key() but in check_form_key() - // it won't allow the current time to be the same as the timestamp it requires. - // As such, automated scripts like this one have to somehow bypass this without being able to change - // the timestamp. The only way I can think to do so is using sleep() - sleep(1); - // I use a request because the form submission method does not allow you to send data that is not // contained in one of the actual form fields that the browser sees (i.e. it ignores "hidden" inputs) // Instead, I send it as a request with the submit button "post" set to true. @@ -96,9 +89,8 @@ class phpbb_functional_posting_test extends phpbb_functional_test_case } } - // For reasoning behind the following two commands, see the test_post_new_topic() test + // For reasoning behind the following command, see the test_post_new_topic() test $form_data['lastclick'] = 0; - sleep(1); // Submit the post $crawler = $this->client->request('POST', 'posting.php', $form_data); From 4dd1bbc5879ae5fcae04341a9152e0366ed68bdd Mon Sep 17 00:00:00 2001 From: David King Date: Sat, 1 Sep 2012 10:53:01 -0400 Subject: [PATCH 0750/1142] [task/functional] Fixed DEBUG_TEST related issues PHPBB3-10758 --- phpBB/includes/functions_install.php | 6 +++++- tests/test_framework/phpbb_functional_test_case.php | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php index 2e10db6b24..eae136808c 100644 --- a/phpBB/includes/functions_install.php +++ b/phpBB/includes/functions_install.php @@ -586,7 +586,6 @@ function phpbb_create_config_file_data($data, $dbms, $load_extensions, $debug = { $config_data .= "@define('DEBUG', true);\n"; $config_data .= "@define('DEBUG_EXTRA', true);\n"; - $config_data .= "@define('DEBUG_TEST', true);\n"; } else { @@ -594,6 +593,11 @@ function phpbb_create_config_file_data($data, $dbms, $load_extensions, $debug = $config_data .= "// @define('DEBUG_EXTRA', true);\n"; } + if ($debug_test) + { + $config_data .= "@define('DEBUG_TEST', true);\n"; + } + return $config_data; } diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index 2423299b7c..d35913e415 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -140,7 +140,7 @@ class phpbb_functional_test_case extends phpbb_test_case $this->do_request('create_table', $data); $this->do_request('config_file', $data); - file_put_contents($phpbb_root_path . "config.$phpEx", phpbb_create_config_file_data($data, self::$config['dbms'], array(), true)); + file_put_contents($phpbb_root_path . "config.$phpEx", phpbb_create_config_file_data($data, self::$config['dbms'], array(), true, true)); $this->do_request('final', $data); copy($phpbb_root_path . "config.$phpEx", $phpbb_root_path . "config_test.$phpEx"); From 0c35ee2769d3945f25275d5ee7a130b7596d289f Mon Sep 17 00:00:00 2001 From: "Michael C." Date: Sat, 1 Sep 2012 17:32:59 +0200 Subject: [PATCH 0751/1142] [ticket/10631] Fix a spelling typo PHPBB3-10631 --- phpBB/includes/acp/acp_extensions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index 5a537aaa42..a0bcf62ecc 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -51,7 +51,7 @@ class acp_extensions $ext_name = ''; } - // If they've specificed an extension, let's load the metadata manager and validate it. + // If they've specified an extension, let's load the metadata manager and validate it. if ($ext_name) { $md_manager = new phpbb_extension_metadata_manager($ext_name, $db, $phpbb_extension_manager, $phpbb_root_path, ".$phpEx", $template, $config); From ed052290a70ae3a42f9e7c59f3dca95aba0c0ac7 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 1 Sep 2012 17:40:19 +0200 Subject: [PATCH 0752/1142] [ticket/11082] Remove executable permission from redis driver file PHPBB3-11082 --- phpBB/includes/cache/driver/redis.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 phpBB/includes/cache/driver/redis.php diff --git a/phpBB/includes/cache/driver/redis.php b/phpBB/includes/cache/driver/redis.php old mode 100755 new mode 100644 From 8741bcdaf5bb815a228188b4dfe4e3beebcca3b3 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 1 Sep 2012 17:51:27 +0200 Subject: [PATCH 0753/1142] [ticket/11083] Mark memory cache driver as abstract PHPBB3-11083 --- phpBB/includes/cache/driver/memory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php index 92971c6cb2..e0771ab1d3 100644 --- a/phpBB/includes/cache/driver/memory.php +++ b/phpBB/includes/cache/driver/memory.php @@ -19,7 +19,7 @@ if (!defined('IN_PHPBB')) * ACM Abstract Memory Class * @package acm */ -class phpbb_cache_driver_memory extends phpbb_cache_driver_base +abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base { var $key_prefix; From 7ed7b19a1f1c3732b561ec3c8a4a39f115b5e6ac Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 1 Sep 2012 19:12:57 +0200 Subject: [PATCH 0754/1142] [feature/dic] Remove unneeded newline PHPBB3-10739 --- phpBB/common.php | 1 - 1 file changed, 1 deletion(-) diff --git a/phpBB/common.php b/phpBB/common.php index 6ca495a7e3..281eb88c4d 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -86,7 +86,6 @@ require($phpbb_root_path . 'includes/constants.' . $phpEx); require($phpbb_root_path . 'includes/db/' . ltrim($dbms, 'dbal_') . '.' . $phpEx); require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); - // Set PHP error handler to ours set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); From 282a80077d4630ba59043fffe59e8a7ce8619ecb Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 1 Sep 2012 19:17:01 +0200 Subject: [PATCH 0755/1142] [feature/dic] Spaces to tabs, add useless docblocks Fully documents the constructors of the processors and the cron tasks. PHPBB3-10739 --- .../cron/task/core/prune_all_forums.php | 8 ++ phpBB/includes/cron/task/core/prune_forum.php | 8 ++ phpBB/includes/cron/task/core/queue.php | 7 ++ phpBB/includes/cron/task/core/tidy_cache.php | 6 ++ .../includes/cron/task/core/tidy_database.php | 7 ++ phpBB/includes/cron/task/core/tidy_search.php | 10 +++ .../includes/cron/task/core/tidy_sessions.php | 6 ++ .../includes/cron/task/core/tidy_warnings.php | 7 ++ phpBB/includes/di/processor/config.php | 82 +++++++++++-------- phpBB/includes/di/processor/ext.php | 47 +++++++---- phpBB/includes/di/processor/interface.php | 9 +- 11 files changed, 143 insertions(+), 54 deletions(-) diff --git a/phpBB/includes/cron/task/core/prune_all_forums.php b/phpBB/includes/cron/task/core/prune_all_forums.php index 5164087b68..252e16e57d 100644 --- a/phpBB/includes/cron/task/core/prune_all_forums.php +++ b/phpBB/includes/cron/task/core/prune_all_forums.php @@ -31,6 +31,14 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base protected $config; protected $db; + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext The PHP extension + * @param phpbb_config $config The config + * @param dbal $db The db connection + */ public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, dbal $db) { $this->phpbb_root_path = $phpbb_root_path; diff --git a/phpBB/includes/cron/task/core/prune_forum.php b/phpBB/includes/cron/task/core/prune_forum.php index 8bb13ffe36..41d60af921 100644 --- a/phpBB/includes/cron/task/core/prune_forum.php +++ b/phpBB/includes/cron/task/core/prune_forum.php @@ -41,6 +41,14 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p */ protected $forum_data; + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext The PHP extension + * @param phpbb_config $config The config + * @param dbal $db The db connection + */ public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, dbal $db) { $this->phpbb_root_path = $phpbb_root_path; diff --git a/phpBB/includes/cron/task/core/queue.php b/phpBB/includes/cron/task/core/queue.php index 3278ce9d76..c765660906 100644 --- a/phpBB/includes/cron/task/core/queue.php +++ b/phpBB/includes/cron/task/core/queue.php @@ -26,6 +26,13 @@ class phpbb_cron_task_core_queue extends phpbb_cron_task_base protected $php_ext; protected $config; + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext The PHP extension + * @param phpbb_config $config The config + */ public function __construct($phpbb_root_path, $php_ext, phpbb_config $config) { $this->phpbb_root_path = $phpbb_root_path; diff --git a/phpBB/includes/cron/task/core/tidy_cache.php b/phpBB/includes/cron/task/core/tidy_cache.php index 573243c166..6017eea561 100644 --- a/phpBB/includes/cron/task/core/tidy_cache.php +++ b/phpBB/includes/cron/task/core/tidy_cache.php @@ -25,6 +25,12 @@ class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base protected $config; protected $cache; + /** + * Constructor. + * + * @param phpbb_config $config The config + * @param phpbb_cache_driver_interface $cache The cache driver + */ public function __construct(phpbb_config $config, phpbb_cache_driver_interface $cache) { $this->config = $config; diff --git a/phpBB/includes/cron/task/core/tidy_database.php b/phpBB/includes/cron/task/core/tidy_database.php index c9f81cbb51..1d256f964f 100644 --- a/phpBB/includes/cron/task/core/tidy_database.php +++ b/phpBB/includes/cron/task/core/tidy_database.php @@ -26,6 +26,13 @@ class phpbb_cron_task_core_tidy_database extends phpbb_cron_task_base protected $php_ext; protected $config; + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext The PHP extension + * @param phpbb_config $config The config + */ public function __construct($phpbb_root_path, $php_ext, phpbb_config $config) { $this->phpbb_root_path = $phpbb_root_path; diff --git a/phpBB/includes/cron/task/core/tidy_search.php b/phpBB/includes/cron/task/core/tidy_search.php index 00af293b6d..2e5f3d79d5 100644 --- a/phpBB/includes/cron/task/core/tidy_search.php +++ b/phpBB/includes/cron/task/core/tidy_search.php @@ -31,6 +31,16 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base protected $db; protected $user; + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext The PHP extension + * @param phpbb_auth $auth The auth + * @param phpbb_config $config The config + * @param dbal $db The db connection + * @param phpbb_user $user The user + */ public function __construct($phpbb_root_path, $php_ext, phpbb_auth $auth, phpbb_config $config, dbal $db, phpbb_user $user) { $this->phpbb_root_path = $phpbb_root_path; diff --git a/phpBB/includes/cron/task/core/tidy_sessions.php b/phpBB/includes/cron/task/core/tidy_sessions.php index 695a537274..13531aa30b 100644 --- a/phpBB/includes/cron/task/core/tidy_sessions.php +++ b/phpBB/includes/cron/task/core/tidy_sessions.php @@ -25,6 +25,12 @@ class phpbb_cron_task_core_tidy_sessions extends phpbb_cron_task_base protected $config; protected $user; + /** + * Constructor. + * + * @param phpbb_config $config The config + * @param phpbb_user $user The user + */ public function __construct(phpbb_config $config, phpbb_user $user) { $this->config = $config; diff --git a/phpBB/includes/cron/task/core/tidy_warnings.php b/phpBB/includes/cron/task/core/tidy_warnings.php index acffd12052..8dd0674fe5 100644 --- a/phpBB/includes/cron/task/core/tidy_warnings.php +++ b/phpBB/includes/cron/task/core/tidy_warnings.php @@ -28,6 +28,13 @@ class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base protected $php_ext; protected $config; + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext The PHP extension + * @param phpbb_config $config The config + */ public function __construct($phpbb_root_path, $php_ext, phpbb_config $config) { $this->phpbb_root_path = $phpbb_root_path; diff --git a/phpBB/includes/di/processor/config.php b/phpBB/includes/di/processor/config.php index 1a5ec15854..22b6252a6d 100644 --- a/phpBB/includes/di/processor/config.php +++ b/phpBB/includes/di/processor/config.php @@ -12,51 +12,65 @@ */ if (!defined('IN_PHPBB')) { - exit; + exit; } use Symfony\Component\DependencyInjection\ContainerBuilder; +/** +* Configure the container for phpBB's services though +* user-defined parameters defined in the config.php file. +*/ class phpbb_di_processor_config implements phpbb_di_processor_interface { - private $config_file; - private $phpbb_root_path; - private $php_ext; + private $config_file; + private $phpbb_root_path; + private $php_ext; - public function __construct($config_file, $phpbb_root_path, $php_ext) - { - $this->config_file = $config_file; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - } + /** + * Constructor. + * + * @param string $config_file The config file + * @param string $phpbb_root_path The root path + * @param string $php_ext The PHP extension + */ + public function __construct($config_file, $phpbb_root_path, $php_ext) + { + $this->config_file = $config_file; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } - public function process(ContainerBuilder $container) - { - require $this->config_file; + /** + * @inheritdoc + */ + public function process(ContainerBuilder $container) + { + require $this->config_file; - $container->setParameter('core.root_path', $this->phpbb_root_path); - $container->setParameter('core.php_ext', $this->php_ext); + $container->setParameter('core.root_path', $this->phpbb_root_path); + $container->setParameter('core.php_ext', $this->php_ext); - $container->setParameter('core.table_prefix', $table_prefix); - $container->setParameter('cache.driver.class', $this->fix_acm_type($acm_type)); - $container->setParameter('dbal.driver.class', 'dbal_'.$dbms); - $container->setParameter('dbal.dbhost', $dbhost); - $container->setParameter('dbal.dbuser', $dbuser); - $container->setParameter('dbal.dbpasswd', $dbpasswd); - $container->setParameter('dbal.dbname', $dbname); - $container->setParameter('dbal.dbport', $dbport); - $container->setParameter('dbal.new_link', defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK); + $container->setParameter('core.table_prefix', $table_prefix); + $container->setParameter('cache.driver.class', $this->fix_acm_type($acm_type)); + $container->setParameter('dbal.driver.class', 'dbal_'.$dbms); + $container->setParameter('dbal.dbhost', $dbhost); + $container->setParameter('dbal.dbuser', $dbuser); + $container->setParameter('dbal.dbpasswd', $dbpasswd); + $container->setParameter('dbal.dbname', $dbname); + $container->setParameter('dbal.dbport', $dbport); + $container->setParameter('dbal.new_link', defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK); - $container->set('container', $container); - } + $container->set('container', $container); + } - protected function fix_acm_type($acm_type) - { - if (preg_match('#^[a-z]+$#', $acm_type)) - { - return 'phpbb_cache_driver_'.$acm_type; - } + protected function fix_acm_type($acm_type) + { + if (preg_match('#^[a-z]+$#', $acm_type)) + { + return 'phpbb_cache_driver_'.$acm_type; + } - return $acm_type; - } + return $acm_type; + } } diff --git a/phpBB/includes/di/processor/ext.php b/phpBB/includes/di/processor/ext.php index 04a586a086..e69a3d73b3 100644 --- a/phpBB/includes/di/processor/ext.php +++ b/phpBB/includes/di/processor/ext.php @@ -12,32 +12,43 @@ */ if (!defined('IN_PHPBB')) { - exit; + exit; } use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +/** +* Load the service configurations from all extensions into the container. +*/ class phpbb_di_processor_ext implements phpbb_di_processor_interface { - private $extension_manager; + private $extension_manager; - public function __construct($extension_manager) - { - $this->extension_manager = $extension_manager; - } + /** + * Constructor. + * + * @param string $extension_manager The extension manager + */ + public function __construct($extension_manager) + { + $this->extension_manager = $extension_manager; + } - public function process(ContainerBuilder $container) - { - $enabled_exts = $this->extension_manager->all_enabled(); - foreach ($enabled_exts as $name => $path) - { - if (file_exists($path . '/config/services.yml')) - { - $loader = new YamlFileLoader($container, new FileLocator($path . '/config')); - $loader->load('services.yml'); - } - } - } + /** + * @inheritdoc + */ + public function process(ContainerBuilder $container) + { + $enabled_exts = $this->extension_manager->all_enabled(); + foreach ($enabled_exts as $name => $path) + { + if (file_exists($path . '/config/services.yml')) + { + $loader = new YamlFileLoader($container, new FileLocator($path . '/config')); + $loader->load('services.yml'); + } + } + } } diff --git a/phpBB/includes/di/processor/interface.php b/phpBB/includes/di/processor/interface.php index 51bd85a076..b8563791cc 100644 --- a/phpBB/includes/di/processor/interface.php +++ b/phpBB/includes/di/processor/interface.php @@ -12,12 +12,17 @@ */ if (!defined('IN_PHPBB')) { - exit; + exit; } use Symfony\Component\DependencyInjection\ContainerBuilder; interface phpbb_di_processor_interface { - public function process(ContainerBuilder $container); + /** + * Mutate the container. + * + * @param ContainerBuilder $container The container + */ + public function process(ContainerBuilder $container); } From 81f7f28cc33d896973c2912097103ea84fa14114 Mon Sep 17 00:00:00 2001 From: Unknown Bliss Date: Sat, 1 Sep 2012 21:30:35 +0100 Subject: [PATCH 0756/1142] [ticket/10631] Removing un-needed TODOs that are no longer needed. PHPBB3-10631 --- phpBB/includes/extension/metadata_manager.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php index 8c570830fe..ea85bd3c4e 100644 --- a/phpBB/includes/extension/metadata_manager.php +++ b/phpBB/includes/extension/metadata_manager.php @@ -95,7 +95,6 @@ class phpbb_extension_metadata_manager return ($this->validate('name')) ? $this->metadata['name'] : false; } break; - // TODO: Add remaining cases as needed } } @@ -153,9 +152,6 @@ class phpbb_extension_metadata_manager */ private function clean_metadata_array() { -// TODO: Remove all parts of the array we don't want or shouldn't be there due to nub mod authors -// $this->metadata = $metadata_finished; - return $this->metadata; } From b3cd5a649be62f175de651a16ae02c5f709ca2f4 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Mon, 3 Sep 2012 13:32:33 -0500 Subject: [PATCH 0757/1142] [ticket/8713] Do not trim login inputs Create a function to request variables which are not trimmed. All requests for passwords (except forum passwords) now use the untrimmed request function. PHPBB3-8713 --- phpBB/includes/acp/acp_language.php | 6 +- phpBB/includes/acp/acp_users.php | 6 +- phpBB/includes/functions.php | 4 +- phpBB/includes/request/request.php | 63 +++++++++++++++++++++ phpBB/includes/request/type_cast_helper.php | 22 +++++-- phpBB/includes/ucp/ucp_profile.php | 6 +- phpBB/includes/ucp/ucp_register.php | 4 +- phpBB/install/install_update.php | 4 +- tests/request/type_cast_helper_test.php | 10 ++++ 9 files changed, 104 insertions(+), 21 deletions(-) diff --git a/phpBB/includes/acp/acp_language.php b/phpBB/includes/acp/acp_language.php index 2b19f93c75..87cf605d8e 100644 --- a/phpBB/includes/acp/acp_language.php +++ b/phpBB/includes/acp/acp_language.php @@ -100,11 +100,11 @@ class acp_language switch ($method) { case 'ftp': - $transfer = new ftp(request_var('host', ''), request_var('username', ''), request_var('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new ftp(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); break; case 'ftp_fsock': - $transfer = new ftp_fsock(request_var('host', ''), request_var('username', ''), request_var('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new ftp_fsock(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); break; default: @@ -404,7 +404,7 @@ class acp_language trigger_error($user->lang['INVALID_UPLOAD_METHOD'], E_USER_ERROR); } - $transfer = new $method(request_var('host', ''), request_var('username', ''), request_var('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); if (($result = $transfer->open_session()) !== true) { diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index b54257b04a..b9958ed0f1 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -32,7 +32,7 @@ class acp_users { global $config, $db, $user, $auth, $template, $cache; global $phpbb_root_path, $phpbb_admin_path, $phpEx, $table_prefix, $file_uploads; - global $phpbb_dispatcher; + global $phpbb_dispatcher, $request; $user->add_lang(array('posting', 'ucp', 'acp/users')); $this->tpl_name = 'acp_users'; @@ -770,8 +770,8 @@ class acp_users 'username' => utf8_normalize_nfc(request_var('user', $user_row['username'], true)), 'user_founder' => request_var('user_founder', ($user_row['user_type'] == USER_FOUNDER) ? 1 : 0), 'email' => strtolower(request_var('user_email', $user_row['user_email'])), - 'new_password' => request_var('new_password', '', true), - 'password_confirm' => request_var('password_confirm', '', true), + 'new_password' => $request->untrimed_variable('new_password', '', true), + 'password_confirm' => $request->untrimed_variable('password_confirm', '', true), ); // Validation data - we do not check the password complexity setting here diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 834f57a38b..1cdda60855 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3044,11 +3044,11 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa trigger_error('NO_AUTH_ADMIN'); } - $password = request_var('password_' . $credential, '', true); + $password = $request->untrimed_variable('password_' . $credential, '', true); } else { - $password = request_var('password', '', true); + $password = $request->untrimed_variable('password', '', true); } $username = request_var('username', '', true); diff --git a/phpBB/includes/request/request.php b/phpBB/includes/request/request.php index 4e425dbd27..747ca09624 100644 --- a/phpBB/includes/request/request.php +++ b/phpBB/includes/request/request.php @@ -242,6 +242,69 @@ class phpbb_request implements phpbb_request_interface return $var; } + /** + * Get a variable, but without trimming strings + * Same functionality as variable(), except does not run trim() on strings + * All variables in GET or POST requests should be retrieved through this function to maximise security. + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + public function untrimed_variable($var_name, $default, $multibyte, $super_global = phpbb_request_interface::REQUEST) + { + $path = false; + + // deep direct access to multi dimensional arrays + if (is_array($var_name)) + { + $path = $var_name; + // make sure at least the variable name is specified + if (empty($path)) + { + return (is_array($default)) ? array() : $default; + } + // the variable name is the first element on the path + $var_name = array_shift($path); + } + + if (!isset($this->input[$super_global][$var_name])) + { + return (is_array($default)) ? array() : $default; + } + $var = $this->input[$super_global][$var_name]; + + if ($path) + { + // walk through the array structure and find the element we are looking for + foreach ($path as $key) + { + if (is_array($var) && isset($var[$key])) + { + $var = $var[$key]; + } + else + { + return (is_array($default)) ? array() : $default; + } + } + } + + $this->type_cast_helper->recursive_set_var($var, $default, $multibyte, false); + + return $var; + } + /** * Shortcut method to retrieve SERVER variables. * diff --git a/phpBB/includes/request/type_cast_helper.php b/phpBB/includes/request/type_cast_helper.php index 561e8fc251..d3b94aac5a 100644 --- a/phpBB/includes/request/type_cast_helper.php +++ b/phpBB/includes/request/type_cast_helper.php @@ -93,15 +93,23 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i * @param mixed $type The variable type. Will be used with {@link settype()} * @param bool $multibyte Indicates whether string values may contain UTF-8 characters. * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks. + * @param bool $trim Indicates whether string values will be be parsed with trim() + * Default is true */ - public function set_var(&$result, $var, $type, $multibyte = false) + public function set_var(&$result, $var, $type, $multibyte = false, $trim = true) { settype($var, $type); $result = $var; if ($type == 'string') { - $result = trim(str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result)); + $result = str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result); + + if ($trim) + { + $result = trim($result); + } + $result = htmlspecialchars($result, ENT_COMPAT, 'UTF-8'); if ($multibyte) @@ -141,8 +149,10 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i * @param bool $multibyte Indicates whether string keys and values may contain UTF-8 characters. * Default is false, causing all bytes outside the ASCII range (0-127) to * be replaced with question marks. + * @param bool $trim Indicates whether string values will be be parsed with trim() + * Default is true */ - public function recursive_set_var(&$var, $default, $multibyte) + public function recursive_set_var(&$var, $default, $multibyte, $trim = true) { if (is_array($var) !== is_array($default)) { @@ -153,7 +163,7 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i if (!is_array($default)) { $type = gettype($default); - $this->set_var($var, $var, $type, $multibyte); + $this->set_var($var, $var, $type, $multibyte, $trim); } else { @@ -174,9 +184,9 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i foreach ($_var as $k => $v) { - $this->set_var($k, $k, $key_type, $multibyte, $multibyte); + $this->set_var($k, $k, $key_type, $multibyte, $trim); - $this->recursive_set_var($v, $default_value, $multibyte); + $this->recursive_set_var($v, $default_value, $multibyte, $trim); $var[$k] = $v; } } diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index 2ac82fb52f..68d5dd5d65 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -46,9 +46,9 @@ class ucp_profile $data = array( 'username' => utf8_normalize_nfc(request_var('username', $user->data['username'], true)), 'email' => strtolower(request_var('email', $user->data['user_email'])), - 'new_password' => request_var('new_password', '', true), - 'cur_password' => request_var('cur_password', '', true), - 'password_confirm' => request_var('password_confirm', '', true), + 'new_password' => $request->untrimed_variable('new_password', '', true), + 'cur_password' => $request->untrimed_variable('cur_password', '', true), + 'password_confirm' => $request->untrimed_variable('password_confirm', '', true), ); add_form_key('ucp_reg_details'); diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 6ce53a79ab..6fab189a99 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -170,8 +170,8 @@ class ucp_register $data = array( 'username' => utf8_normalize_nfc(request_var('username', '', true)), - 'new_password' => request_var('new_password', '', true), - 'password_confirm' => request_var('password_confirm', '', true), + 'new_password' => $request->untrimed_variable('new_password', '', true), + 'password_confirm' => $request->untrimed_variable('password_confirm', '', true), 'email' => strtolower(request_var('email', '')), 'lang' => basename(request_var('lang', $user->lang_name)), 'tz' => request_var('tz', $timezone), diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 88b00f1cf1..4b5a23e497 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -862,7 +862,7 @@ class install_update extends module $test_connection = false; if ($test_ftp_connection || $submit) { - $transfer = new $method(request_var('host', ''), request_var('username', ''), request_var('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); $test_connection = $transfer->open_session(); // Make sure that the directory is correct by checking for the existence of common.php @@ -948,7 +948,7 @@ class install_update extends module } else { - $transfer = new $method(request_var('host', ''), request_var('username', ''), request_var('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); $transfer->open_session(); } diff --git a/tests/request/type_cast_helper_test.php b/tests/request/type_cast_helper_test.php index d553d5b8cd..f7e5cd873e 100644 --- a/tests/request/type_cast_helper_test.php +++ b/tests/request/type_cast_helper_test.php @@ -48,4 +48,14 @@ class phpbb_type_cast_helper_test extends phpbb_test_case $this->assertEquals($expected, $data); } + + public function test_untrimmed_strings() + { + $data = array(' eviL<3 '); + $expected = array(' eviL<3 '); + + $this->type_cast_helper->recursive_set_var($data, '', true, false); + + $this->assertEquals($expected, $data); + } } From 815cc4a9a3fa8c633b55925eb77f9f3bdbb5de04 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Mon, 3 Sep 2012 18:23:36 -0500 Subject: [PATCH 0758/1142] [ticket/8796] Make function markread obey the $post_time argument Also do a little cleanup of the markread function PHPBB3-8796 --- phpBB/includes/functions.php | 71 ++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 834f57a38b..53f48cb8a7 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1280,6 +1280,10 @@ function phpbb_timezone_select($user, $default = '', $truncate = false) * Marks a topic/forum as read * Marks a topic as posted to * +* @param string $mode (all, topics, topic, post) +* @param int|bool $forum_id Used in all, topics, and topic mode +* @param int|bool $topic_id Used in topic and post mode +* @param int $post_time 0 means current time(), otherwise to set a specific mark time * @param int $user_id can only be used with $mode == 'post' */ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0) @@ -1287,6 +1291,8 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ global $db, $user, $config; global $request; + $post_time = ($post_time === 0) ? time() : (int) $post_time; + if ($mode == 'all') { if ($forum_id === false || !sizeof($forum_id)) @@ -1294,9 +1300,22 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ if ($config['load_db_lastread'] && $user->data['is_registered']) { // Mark all forums read (index page) - $db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}"); - $db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}"); - $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}"); + $tables = array(TOPICS_TRACK_TABLE, FORUMS_TRACK_TABLE); + foreach ($tables as $table) + { + $sql = 'DELETE FROM ' . $table . " + WHERE user_id = {$user->data['user_id']} + AND mark_time < . $post_time"; + $db->sql_query($sql); + } + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastmark = $post_time + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time"; + $db->sql_query($sql); + + $user->data['user_lastmark'] = $post_time; } else if ($config['load_anon_lastread'] || $user->data['is_registered']) { @@ -1306,16 +1325,22 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ unset($tracking_topics['tf']); unset($tracking_topics['t']); unset($tracking_topics['f']); - $tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36); + $tracking_topics['l'] = base_convert($post_time - $config['board_startdate'], 10, 36); - $user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000); + $user->set_cookie('track', tracking_serialize($tracking_topics), $post_time + 31536000); $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking_topics), phpbb_request_interface::COOKIE); unset($tracking_topics); if ($user->data['is_registered']) { - $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}"); + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastmark = $post_time + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time"; + $db->sql_query($sql); + + $user->data['user_lastmark'] = $post_time; } } } @@ -1337,12 +1362,14 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ { $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time AND " . $db->sql_in_set('forum_id', $forum_id); $db->sql_query($sql); $sql = 'SELECT forum_id FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time AND " . $db->sql_in_set('forum_id', $forum_id); $result = $db->sql_query($sql); @@ -1355,9 +1382,10 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ if (sizeof($sql_update)) { - $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . ' - SET mark_time = ' . time() . " + $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . " + SET mark_time = $post_time WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time AND " . $db->sql_in_set('forum_id', $sql_update); $db->sql_query($sql); } @@ -1370,7 +1398,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ $sql_ary[] = array( 'user_id' => (int) $user->data['user_id'], 'forum_id' => (int) $f_id, - 'mark_time' => time() + 'mark_time' => $post_time, ); } @@ -1401,7 +1429,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ unset($tracking['f'][$f_id]); } - $tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36); + $tracking['f'][$f_id] = base_convert($post_time - $config['board_startdate'], 10, 36); } if (isset($tracking['tf']) && empty($tracking['tf'])) @@ -1409,7 +1437,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ unset($tracking['tf']); } - $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000); + $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000); $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), phpbb_request_interface::COOKIE); unset($tracking); @@ -1426,9 +1454,10 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ if ($config['load_db_lastread'] && $user->data['is_registered']) { - $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . ' - SET mark_time = ' . (($post_time) ? $post_time : time()) . " + $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . " + SET mark_time = $post_time WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time AND topic_id = $topic_id"; $db->sql_query($sql); @@ -1441,7 +1470,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ 'user_id' => (int) $user->data['user_id'], 'topic_id' => (int) $topic_id, 'forum_id' => (int) $forum_id, - 'mark_time' => ($post_time) ? (int) $post_time : time(), + 'mark_time' => $post_time, ); $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); @@ -1461,7 +1490,6 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ $tracking['tf'][$forum_id][$topic_id36] = true; } - $post_time = ($post_time) ? $post_time : time(); $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36); // If the cookie grows larger than 10000 characters we will remove the smallest value @@ -1496,8 +1524,13 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ if ($user->data['is_registered']) { - $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10)); - $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}"); + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastmark = $post_time + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time"; + $db->sql_query($sql); + + $user->data['user_lastmark'] = $post_time; } else { @@ -1505,7 +1538,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ } } - $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000); + $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000); $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), phpbb_request_interface::COOKIE); } @@ -1527,7 +1560,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ $sql_ary = array( 'user_id' => (int) $use_user_id, 'topic_id' => (int) $topic_id, - 'topic_posted' => 1 + 'topic_posted' => 1, ); $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); From b9308329cf3c0e6844a35f2d274423c2640887db Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Mon, 3 Sep 2012 18:37:54 -0500 Subject: [PATCH 0759/1142] [ticket/8796] Revert changes to $user->data['lastmark'] The earlier change might change the way some things work (after looking at viewtopic) and I'd rather not risk introducing new bugs, so I'm going to revert those changes to be safe. PHPBB3-8796 --- phpBB/includes/functions.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 53f48cb8a7..26b73e20fe 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1314,8 +1314,6 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ WHERE user_id = {$user->data['user_id']} AND mark_time < $post_time"; $db->sql_query($sql); - - $user->data['user_lastmark'] = $post_time; } else if ($config['load_anon_lastread'] || $user->data['is_registered']) { @@ -1339,8 +1337,6 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ WHERE user_id = {$user->data['user_id']} AND mark_time < $post_time"; $db->sql_query($sql); - - $user->data['user_lastmark'] = $post_time; } } } @@ -1524,13 +1520,13 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ if ($user->data['is_registered']) { + $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10)); + $sql = 'UPDATE ' . USERS_TABLE . " SET user_lastmark = $post_time WHERE user_id = {$user->data['user_id']} AND mark_time < $post_time"; $db->sql_query($sql); - - $user->data['user_lastmark'] = $post_time; } else { From 9cba0b5263a8dcf0ff4cbfd6db2b37cd0cce55f1 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Mon, 3 Sep 2012 18:51:29 -0500 Subject: [PATCH 0760/1142] [ticket/8796] Mark read links updated to include time() in url Submitting the current time() allows us to mark only the topics and forums read up until a certain time (when the user loaded the page). This means that any new posts or topics posted between when the user opened the page and clicked the link are still shown as unread. PHPBB3-8796 --- phpBB/includes/functions_display.php | 6 +++--- phpBB/index.php | 2 +- phpBB/viewforum.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index 881c95907b..e9c7143473 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -59,7 +59,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod if (check_link_hash(request_var('hash', ''), 'global')) { - markread('all'); + markread('all', false, false, request_var('mark_time', 0)); trigger_error( $user->lang['FORUMS_MARKED'] . '

' . @@ -309,7 +309,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod $token = request_var('hash', ''); if (check_link_hash($token, 'global')) { - markread('topics', $forum_ids); + markread('topics', $forum_ids, false, request_var('mark_time', 0)); $message = sprintf($user->lang['RETURN_FORUM'], '', ''); meta_refresh(3, $redirect); @@ -551,7 +551,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod } $template->assign_vars(array( - 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . '&f=' . $root_data['forum_id'] . '&mark=forums') : '', + 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . '&f=' . $root_data['forum_id'] . '&mark=forums&mark_time=' . time()) : '', 'S_HAS_SUBFORUM' => ($visible_forums) ? true : false, 'L_SUBFORUM' => ($visible_forums == 1) ? $user->lang['SUBFORUM'] : $user->lang['SUBFORUMS'], 'LAST_POST_IMG' => $user->img('icon_topic_latest', 'VIEW_LATEST_POST'), diff --git a/phpBB/index.php b/phpBB/index.php index 0ac8034d7f..66e1b2114b 100644 --- a/phpBB/index.php +++ b/phpBB/index.php @@ -167,7 +167,7 @@ $template->assign_vars(array( 'S_LOGIN_ACTION' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'), 'S_DISPLAY_BIRTHDAY_LIST' => ($config['load_birthdays']) ? true : false, - 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}index.$phpEx", 'hash=' . generate_link_hash('global') . '&mark=forums') : '', + 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}index.$phpEx", 'hash=' . generate_link_hash('global') . '&mark=forums&mark_time=' . time()) : '', 'U_MCP' => ($auth->acl_get('m_') || $auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main&mode=front', true, $user->session_id) : '') ); diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 03c2bb286f..83e5d4caa5 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -176,7 +176,7 @@ if ($mark_read == 'topics') $token = request_var('hash', ''); if (check_link_hash($token, 'global')) { - markread('topics', array($forum_id)); + markread('topics', array($forum_id), false, request_var('mark_time', 0)); } $redirect_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id); meta_refresh(3, $redirect_url); @@ -340,7 +340,7 @@ $template->assign_vars(array( 'U_MCP' => ($auth->acl_get('m_', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "f=$forum_id&i=main&mode=forum_view", true, $user->session_id) : '', 'U_POST_NEW_TOPIC' => ($auth->acl_get('f_post', $forum_id) || $user->data['user_id'] == ANONYMOUS) ? append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=post&f=' . $forum_id) : '', 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id" . ((strlen($u_sort_param)) ? "&$u_sort_param" : '') . (($start == 0) ? '' : "&start=$start")), - 'U_MARK_TOPICS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . "&f=$forum_id&mark=topics") : '', + 'U_MARK_TOPICS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . "&f=$forum_id&mark=topics&mark_time=" . time()) : '', )); // Grab icons From fccbf09e4aa9034afba5d5992d773b209f59bba7 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Mon, 3 Sep 2012 18:58:38 -0500 Subject: [PATCH 0761/1142] [ticket/8796] Fix a few issues with the previous commits Fix an SQL error and the redirect url generated PHPBB3-8796 --- phpBB/includes/functions.php | 6 +++--- phpBB/includes/functions_display.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 26b73e20fe..13cb15d73d 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1305,14 +1305,14 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ { $sql = 'DELETE FROM ' . $table . " WHERE user_id = {$user->data['user_id']} - AND mark_time < . $post_time"; + AND mark_time < $post_time"; $db->sql_query($sql); } $sql = 'UPDATE ' . USERS_TABLE . " SET user_lastmark = $post_time WHERE user_id = {$user->data['user_id']} - AND mark_time < $post_time"; + AND user_lastmark < $post_time"; $db->sql_query($sql); } else if ($config['load_anon_lastread'] || $user->data['is_registered']) @@ -1335,7 +1335,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ $sql = 'UPDATE ' . USERS_TABLE . " SET user_lastmark = $post_time WHERE user_id = {$user->data['user_id']} - AND mark_time < $post_time"; + AND user_lastmark < $post_time"; $db->sql_query($sql); } } diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index e9c7143473..39ec8445c5 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -54,7 +54,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod // Handle marking everything read if ($mark_read == 'all') { - $redirect = build_url(array('mark', 'hash')); + $redirect = build_url(array('mark', 'hash', 'mark_time')); meta_refresh(3, $redirect); if (check_link_hash(request_var('hash', ''), 'global')) @@ -305,7 +305,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod // Handle marking posts if ($mark_read == 'forums') { - $redirect = build_url(array('mark', 'hash')); + $redirect = build_url(array('mark', 'hash', 'mark_time')); $token = request_var('hash', ''); if (check_link_hash($token, 'global')) { From c93b3827dc0644ea4aece1af661224aaa68a4cae Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Thu, 6 Sep 2012 18:27:49 -0500 Subject: [PATCH 0762/1142] [ticket/11092] phpbb_gen_download_links strict standards errors Two strict standards errors in phpbb_gen_download_links PHPBB3-11092 --- phpBB/includes/functions_compress.php | 4 +++- phpBB/includes/functions_display.php | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions_compress.php b/phpBB/includes/functions_compress.php index 8e07e6d1b8..4675394633 100644 --- a/phpBB/includes/functions_compress.php +++ b/phpBB/includes/functions_compress.php @@ -159,8 +159,10 @@ class compress /** * Return available methods + * + * @return array Array of strings of available compression methods (.tar, .tar.gz, .zip, etc.) */ - function methods() + public static function methods() { $methods = array('.tar'); $available_methods = array('.tar.gz' => 'zlib', '.tar.bz2' => 'bz2', '.zip' => 'zlib'); diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index 881c95907b..8328b9ee7a 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -1410,7 +1410,8 @@ function phpbb_gen_download_links($param_key, $param_val, $phpbb_root_path, $php foreach ($methods as $method) { - $type = array_pop(explode('.', $method)); + $exploded = explode('.', $method); + $type = array_pop($exploded); $params = array('archive' => $method); $params[$param_key] = $param_val; From 552233d8fd27ca09fbed555582a9880771205929 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 01:03:38 +0200 Subject: [PATCH 0763/1142] [ticket/11100] Mark can_use_ssl() and can_use_tls() as static. PHPBB3-11100 --- phpBB/includes/functions_jabber.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/includes/functions_jabber.php b/phpBB/includes/functions_jabber.php index d76309d5bb..3d8e403f4b 100644 --- a/phpBB/includes/functions_jabber.php +++ b/phpBB/includes/functions_jabber.php @@ -68,7 +68,7 @@ class jabber } $this->password = $password; - $this->use_ssl = ($use_ssl && $this->can_use_ssl()) ? true : false; + $this->use_ssl = ($use_ssl && self::can_use_ssl()) ? true : false; // Change port if we use SSL if ($this->port == 5222 && $this->use_ssl) @@ -83,7 +83,7 @@ class jabber /** * Able to use the SSL functionality? */ - function can_use_ssl() + static public function can_use_ssl() { // Will not work with PHP >= 5.2.1 or < 5.2.3RC2 until timeout problem with ssl hasn't been fixed (http://bugs.php.net/41236) return ((version_compare(PHP_VERSION, '5.2.1', '<') || version_compare(PHP_VERSION, '5.2.3RC2', '>=')) && @extension_loaded('openssl')) ? true : false; @@ -92,7 +92,7 @@ class jabber /** * Able to use TLS? */ - function can_use_tls() + static public function can_use_tls() { if (!@extension_loaded('openssl') || !function_exists('stream_socket_enable_crypto') || !function_exists('stream_get_meta_data') || !function_exists('socket_set_blocking') || !function_exists('stream_get_wrappers')) { @@ -442,7 +442,7 @@ class jabber } // Let's use TLS if SSL is not enabled and we can actually use it - if (!$this->session['ssl'] && $this->can_use_tls() && $this->can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls'])) + if (!$this->session['ssl'] && self::can_use_tls() && self::can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls'])) { $this->add_to_log('Switching to TLS.'); $this->send("\n"); From 06c3868c27c394747bbaa5a8dac6ed83b5d61951 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 13:40:28 +0200 Subject: [PATCH 0764/1142] [ticket/8713] Adjust test method name to other recursive_set_var() tests. PHPBB3-8713 --- tests/request/type_cast_helper_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/request/type_cast_helper_test.php b/tests/request/type_cast_helper_test.php index f7e5cd873e..94b6e9146f 100644 --- a/tests/request/type_cast_helper_test.php +++ b/tests/request/type_cast_helper_test.php @@ -49,7 +49,7 @@ class phpbb_type_cast_helper_test extends phpbb_test_case $this->assertEquals($expected, $data); } - public function test_untrimmed_strings() + public function test_nested_untrimmed_recursive_set_var() { $data = array(' eviL<3 '); $expected = array(' eviL<3 '); From 2c41b9062a6a8335aa1bfa7c80077f4ae33d33e4 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 13:41:51 +0200 Subject: [PATCH 0765/1142] [ticket/8713] Use correct parameter for nested data. PHPBB3-8713 --- tests/request/type_cast_helper_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/request/type_cast_helper_test.php b/tests/request/type_cast_helper_test.php index 94b6e9146f..176638dc44 100644 --- a/tests/request/type_cast_helper_test.php +++ b/tests/request/type_cast_helper_test.php @@ -54,7 +54,7 @@ class phpbb_type_cast_helper_test extends phpbb_test_case $data = array(' eviL<3 '); $expected = array(' eviL<3 '); - $this->type_cast_helper->recursive_set_var($data, '', true, false); + $this->type_cast_helper->recursive_set_var($data, array(0 => ''), true, false); $this->assertEquals($expected, $data); } From 4550fff55a10be737b76275ae5323675ab1c3939 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 13:43:14 +0200 Subject: [PATCH 0766/1142] [ticket/8713] Use \t in double quotes instead of tabs. PHPBB3-8713 --- tests/request/type_cast_helper_test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/request/type_cast_helper_test.php b/tests/request/type_cast_helper_test.php index 176638dc44..8203703639 100644 --- a/tests/request/type_cast_helper_test.php +++ b/tests/request/type_cast_helper_test.php @@ -51,8 +51,8 @@ class phpbb_type_cast_helper_test extends phpbb_test_case public function test_nested_untrimmed_recursive_set_var() { - $data = array(' eviL<3 '); - $expected = array(' eviL<3 '); + $data = array(" eviL<3\t\t"); + $expected = array(" eviL<3\t\t"); $this->type_cast_helper->recursive_set_var($data, array(0 => ''), true, false); From 160c49351b5ce7d2d811a388a4630ec37258bb8f Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 13:44:50 +0200 Subject: [PATCH 0767/1142] [ticket/8713] Add simple (non-nested) test case for untrimmed set_var(). PHPBB3-8713 --- tests/request/type_cast_helper_test.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/request/type_cast_helper_test.php b/tests/request/type_cast_helper_test.php index 8203703639..f41db005af 100644 --- a/tests/request/type_cast_helper_test.php +++ b/tests/request/type_cast_helper_test.php @@ -49,6 +49,16 @@ class phpbb_type_cast_helper_test extends phpbb_test_case $this->assertEquals($expected, $data); } + public function test_simple_untrimmed_recursive_set_var() + { + $data = " eviL<3\t\t"; + $expected = " eviL<3\t\t"; + + $this->type_cast_helper->recursive_set_var($data, '', true, false); + + $this->assertEquals($expected, $data); + } + public function test_nested_untrimmed_recursive_set_var() { $data = array(" eviL<3\t\t"); From 798033075ba0bbef8f43c542ca05aae776747917 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 14:07:06 +0200 Subject: [PATCH 0768/1142] [ticket/8713] Always trim array keys. PHPBB3-8713 --- phpBB/includes/request/type_cast_helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/request/type_cast_helper.php b/phpBB/includes/request/type_cast_helper.php index d3b94aac5a..3039647bfa 100644 --- a/phpBB/includes/request/type_cast_helper.php +++ b/phpBB/includes/request/type_cast_helper.php @@ -184,7 +184,7 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i foreach ($_var as $k => $v) { - $this->set_var($k, $k, $key_type, $multibyte, $trim); + $this->set_var($k, $k, $key_type, $multibyte); $this->recursive_set_var($v, $default_value, $multibyte, $trim); $var[$k] = $v; From c3e0d1b6d12f07df88e31ffd896b275e65b788eb Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 14:07:42 +0200 Subject: [PATCH 0769/1142] [ticket/8713] Fix type_cast_helper.php doc blocks: Add punctuation etc. PHPBB3-8713 --- phpBB/includes/request/type_cast_helper.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/includes/request/type_cast_helper.php b/phpBB/includes/request/type_cast_helper.php index 3039647bfa..1a5274ed14 100644 --- a/phpBB/includes/request/type_cast_helper.php +++ b/phpBB/includes/request/type_cast_helper.php @@ -93,8 +93,8 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i * @param mixed $type The variable type. Will be used with {@link settype()} * @param bool $multibyte Indicates whether string values may contain UTF-8 characters. * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks. - * @param bool $trim Indicates whether string values will be be parsed with trim() - * Default is true + * @param bool $trim Indicates whether trim() should be applied to string values. + * Default is true. */ public function set_var(&$result, $var, $type, $multibyte = false, $trim = true) { @@ -149,8 +149,8 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i * @param bool $multibyte Indicates whether string keys and values may contain UTF-8 characters. * Default is false, causing all bytes outside the ASCII range (0-127) to * be replaced with question marks. - * @param bool $trim Indicates whether string values will be be parsed with trim() - * Default is true + * @param bool $trim Indicates whether trim() should be applied to string values. + * Default is true. */ public function recursive_set_var(&$var, $default, $multibyte, $trim = true) { From b62c37c5799d4b9e018358c38a731d6664acadf1 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 14:09:12 +0200 Subject: [PATCH 0770/1142] [ticket/8713] DRY: variable() and untrimed_variable() into a protected method. PHPBB3-8713 --- phpBB/includes/request/request.php | 144 +++++++++++++---------------- 1 file changed, 64 insertions(+), 80 deletions(-) diff --git a/phpBB/includes/request/request.php b/phpBB/includes/request/request.php index 747ca09624..c6b6610af5 100644 --- a/phpBB/includes/request/request.php +++ b/phpBB/includes/request/request.php @@ -200,46 +200,7 @@ class phpbb_request implements phpbb_request_interface */ public function variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST) { - $path = false; - - // deep direct access to multi dimensional arrays - if (is_array($var_name)) - { - $path = $var_name; - // make sure at least the variable name is specified - if (empty($path)) - { - return (is_array($default)) ? array() : $default; - } - // the variable name is the first element on the path - $var_name = array_shift($path); - } - - if (!isset($this->input[$super_global][$var_name])) - { - return (is_array($default)) ? array() : $default; - } - $var = $this->input[$super_global][$var_name]; - - if ($path) - { - // walk through the array structure and find the element we are looking for - foreach ($path as $key) - { - if (is_array($var) && isset($var[$key])) - { - $var = $var[$key]; - } - else - { - return (is_array($default)) ? array() : $default; - } - } - } - - $this->type_cast_helper->recursive_set_var($var, $default, $multibyte); - - return $var; + return $this->_variable($var_name, $default, $multibyte, $super_global, true); } /** @@ -263,46 +224,7 @@ class phpbb_request implements phpbb_request_interface */ public function untrimed_variable($var_name, $default, $multibyte, $super_global = phpbb_request_interface::REQUEST) { - $path = false; - - // deep direct access to multi dimensional arrays - if (is_array($var_name)) - { - $path = $var_name; - // make sure at least the variable name is specified - if (empty($path)) - { - return (is_array($default)) ? array() : $default; - } - // the variable name is the first element on the path - $var_name = array_shift($path); - } - - if (!isset($this->input[$super_global][$var_name])) - { - return (is_array($default)) ? array() : $default; - } - $var = $this->input[$super_global][$var_name]; - - if ($path) - { - // walk through the array structure and find the element we are looking for - foreach ($path as $key) - { - if (is_array($var) && isset($var[$key])) - { - $var = $var[$key]; - } - else - { - return (is_array($default)) ? array() : $default; - } - } - } - - $this->type_cast_helper->recursive_set_var($var, $default, $multibyte, false); - - return $var; + return $this->_variable($var_name, $default, $multibyte, $super_global, false); } /** @@ -414,4 +336,66 @@ class phpbb_request implements phpbb_request_interface return array_keys($this->input[$super_global]); } + + /** + * Helper function used by variable() and untrimed_variable(). + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * @param bool $trim Indicates whether trim() should be applied to string values. + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + protected function _variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST, $trim = true) + { + $path = false; + + // deep direct access to multi dimensional arrays + if (is_array($var_name)) + { + $path = $var_name; + // make sure at least the variable name is specified + if (empty($path)) + { + return (is_array($default)) ? array() : $default; + } + // the variable name is the first element on the path + $var_name = array_shift($path); + } + + if (!isset($this->input[$super_global][$var_name])) + { + return (is_array($default)) ? array() : $default; + } + $var = $this->input[$super_global][$var_name]; + + if ($path) + { + // walk through the array structure and find the element we are looking for + foreach ($path as $key) + { + if (is_array($var) && isset($var[$key])) + { + $var = $var[$key]; + } + else + { + return (is_array($default)) ? array() : $default; + } + } + } + + $this->type_cast_helper->recursive_set_var($var, $default, $multibyte, $trim); + + return $var; + } } From f2607fc9e80c6f9ad7543b7be5ea6f294aa6c40a Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 14:15:56 +0200 Subject: [PATCH 0771/1142] [ticket/8713] Rename untrimed_variable() to untrimmed_variable(). PHPBB3-8713 --- phpBB/includes/acp/acp_language.php | 6 +++--- phpBB/includes/acp/acp_users.php | 4 ++-- phpBB/includes/functions.php | 4 ++-- phpBB/includes/request/request.php | 4 ++-- phpBB/includes/ucp/ucp_profile.php | 6 +++--- phpBB/includes/ucp/ucp_register.php | 4 ++-- phpBB/install/install_update.php | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/phpBB/includes/acp/acp_language.php b/phpBB/includes/acp/acp_language.php index 87cf605d8e..b5f5ba2312 100644 --- a/phpBB/includes/acp/acp_language.php +++ b/phpBB/includes/acp/acp_language.php @@ -100,11 +100,11 @@ class acp_language switch ($method) { case 'ftp': - $transfer = new ftp(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new ftp(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); break; case 'ftp_fsock': - $transfer = new ftp_fsock(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new ftp_fsock(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); break; default: @@ -404,7 +404,7 @@ class acp_language trigger_error($user->lang['INVALID_UPLOAD_METHOD'], E_USER_ERROR); } - $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); if (($result = $transfer->open_session()) !== true) { diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index b9958ed0f1..2905b84d57 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -770,8 +770,8 @@ class acp_users 'username' => utf8_normalize_nfc(request_var('user', $user_row['username'], true)), 'user_founder' => request_var('user_founder', ($user_row['user_type'] == USER_FOUNDER) ? 1 : 0), 'email' => strtolower(request_var('user_email', $user_row['user_email'])), - 'new_password' => $request->untrimed_variable('new_password', '', true), - 'password_confirm' => $request->untrimed_variable('password_confirm', '', true), + 'new_password' => $request->untrimmed_variable('new_password', '', true), + 'password_confirm' => $request->untrimmed_variable('password_confirm', '', true), ); // Validation data - we do not check the password complexity setting here diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 1cdda60855..a2f8a57938 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3044,11 +3044,11 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa trigger_error('NO_AUTH_ADMIN'); } - $password = $request->untrimed_variable('password_' . $credential, '', true); + $password = $request->untrimmed_variable('password_' . $credential, '', true); } else { - $password = $request->untrimed_variable('password', '', true); + $password = $request->untrimmed_variable('password', '', true); } $username = request_var('username', '', true); diff --git a/phpBB/includes/request/request.php b/phpBB/includes/request/request.php index c6b6610af5..aa62c3b610 100644 --- a/phpBB/includes/request/request.php +++ b/phpBB/includes/request/request.php @@ -222,7 +222,7 @@ class phpbb_request implements phpbb_request_interface * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the * the same as that of $default. If the variable is not set $default is returned. */ - public function untrimed_variable($var_name, $default, $multibyte, $super_global = phpbb_request_interface::REQUEST) + public function untrimmed_variable($var_name, $default, $multibyte, $super_global = phpbb_request_interface::REQUEST) { return $this->_variable($var_name, $default, $multibyte, $super_global, false); } @@ -338,7 +338,7 @@ class phpbb_request implements phpbb_request_interface } /** - * Helper function used by variable() and untrimed_variable(). + * Helper function used by variable() and untrimmed_variable(). * * @param string|array $var_name The form variable's name from which data shall be retrieved. * If the value is an array this may be an array of indizes which will give diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index 68d5dd5d65..db1e3e4722 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -46,9 +46,9 @@ class ucp_profile $data = array( 'username' => utf8_normalize_nfc(request_var('username', $user->data['username'], true)), 'email' => strtolower(request_var('email', $user->data['user_email'])), - 'new_password' => $request->untrimed_variable('new_password', '', true), - 'cur_password' => $request->untrimed_variable('cur_password', '', true), - 'password_confirm' => $request->untrimed_variable('password_confirm', '', true), + 'new_password' => $request->untrimmed_variable('new_password', '', true), + 'cur_password' => $request->untrimmed_variable('cur_password', '', true), + 'password_confirm' => $request->untrimmed_variable('password_confirm', '', true), ); add_form_key('ucp_reg_details'); diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 6fab189a99..5ae92a5cea 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -170,8 +170,8 @@ class ucp_register $data = array( 'username' => utf8_normalize_nfc(request_var('username', '', true)), - 'new_password' => $request->untrimed_variable('new_password', '', true), - 'password_confirm' => $request->untrimed_variable('password_confirm', '', true), + 'new_password' => $request->untrimmed_variable('new_password', '', true), + 'password_confirm' => $request->untrimmed_variable('password_confirm', '', true), 'email' => strtolower(request_var('email', '')), 'lang' => basename(request_var('lang', $user->lang_name)), 'tz' => request_var('tz', $timezone), diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 4b5a23e497..1ecedecce6 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -862,7 +862,7 @@ class install_update extends module $test_connection = false; if ($test_ftp_connection || $submit) { - $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); $test_connection = $transfer->open_session(); // Make sure that the directory is correct by checking for the existence of common.php @@ -948,7 +948,7 @@ class install_update extends module } else { - $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); $transfer->open_session(); } From d543f0ffb1334d0a22c30077a9ed27c098dc4af5 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 8 Sep 2012 14:41:41 +0200 Subject: [PATCH 0772/1142] [ticket/11101] Delay execution of container processors Also fix the name of the ext processor service. PHPBB3-11101 --- phpBB/common.php | 14 +++++++------- phpBB/config/services.yml | 2 +- phpBB/download/file.php | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/phpBB/common.php b/phpBB/common.php index 281eb88c4d..c6bb5c6cfe 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -100,13 +100,6 @@ $processor->process($phpbb_container); $phpbb_class_loader = $phpbb_container->get('class_loader'); $phpbb_class_loader_ext = $phpbb_container->get('class_loader.ext'); -$ids = array_keys($phpbb_container->findTaggedServiceIds('container.processor')); -foreach ($ids as $id) -{ - $processor = $phpbb_container->get($id); - $processor->process($phpbb_container); -} - // set up caching $cache = $phpbb_container->get('cache'); @@ -132,6 +125,13 @@ $phpbb_subscriber_loader = $phpbb_container->get('event.subscriber_loader'); $template = $phpbb_container->get('template'); $phpbb_style = $phpbb_container->get('style'); +$ids = array_keys($phpbb_container->findTaggedServiceIds('container.processor')); +foreach ($ids as $id) +{ + $processor = $phpbb_container->get($id); + $processor->process($phpbb_container); +} + // Add own hook handler require($phpbb_root_path . 'includes/hooks/index.' . $phpEx); $phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('phpbb_template', 'display'))); diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 133a43b77e..c3d2494952 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -89,7 +89,7 @@ services: - .%core.php_ext% - @cache.driver - processor.config: + processor.ext: class: phpbb_di_processor_ext arguments: - @ext.manager diff --git a/phpBB/download/file.php b/phpBB/download/file.php index 8766c6d030..7ed53d54b6 100644 --- a/phpBB/download/file.php +++ b/phpBB/download/file.php @@ -61,13 +61,6 @@ if (isset($_GET['avatar'])) $phpbb_class_loader = $phpbb_container->get('class_loader'); $phpbb_class_loader_ext = $phpbb_container->get('class_loader.ext'); - $ids = array_keys($phpbb_container->findTaggedServiceIds('container.processor')); - foreach ($ids as $id) - { - $processor = $phpbb_container->get($id); - $processor->process($phpbb_container); - } - // set up caching $cache = $phpbb_container->get('cache'); @@ -92,6 +85,13 @@ if (isset($_GET['avatar'])) $phpbb_extension_manager = $phpbb_container->get('ext.manager'); $phpbb_subscriber_loader = $phpbb_container->get('event.subscriber_loader'); + $ids = array_keys($phpbb_container->findTaggedServiceIds('container.processor')); + foreach ($ids as $id) + { + $processor = $phpbb_container->get($id); + $processor->process($phpbb_container); + } + // worst-case default $browser = strtolower($request->header('User-Agent', 'msie 6.0')); From cc0c378caf9bfc480391a9d11d5a4d78c0df097c Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 14:40:35 +0200 Subject: [PATCH 0773/1142] [ticket/8713] Call htmlspecialchars_decode() on transfer (e.g. ftp) passwords. PHPBB3-8713 --- phpBB/includes/acp/acp_language.php | 27 ++++++++++++++++++++++++--- phpBB/install/install_update.php | 18 ++++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/acp/acp_language.php b/phpBB/includes/acp/acp_language.php index b5f5ba2312..2be1ccfc41 100644 --- a/phpBB/includes/acp/acp_language.php +++ b/phpBB/includes/acp/acp_language.php @@ -100,11 +100,25 @@ class acp_language switch ($method) { case 'ftp': - $transfer = new ftp(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new ftp( + request_var('host', ''), + request_var('username', ''), + htmlspecialchars_decode($request->untrimmed_variable('password', '')), + request_var('root_path', ''), + request_var('port', ''), + request_var('timeout', '') + ); break; case 'ftp_fsock': - $transfer = new ftp_fsock(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new ftp_fsock( + request_var('host', ''), + request_var('username', ''), + htmlspecialchars_decode($request->untrimmed_variable('password', '')), + request_var('root_path', ''), + request_var('port', ''), + request_var('timeout', '') + ); break; default: @@ -404,7 +418,14 @@ class acp_language trigger_error($user->lang['INVALID_UPLOAD_METHOD'], E_USER_ERROR); } - $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new $method( + request_var('host', ''), + request_var('username', ''), + htmlspecialchars_decode($request->untrimmed_variable('password', '')), + request_var('root_path', ''), + request_var('port', ''), + request_var('timeout', '') + ); if (($result = $transfer->open_session()) !== true) { diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 1ecedecce6..8c044550f3 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -862,7 +862,14 @@ class install_update extends module $test_connection = false; if ($test_ftp_connection || $submit) { - $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new $method( + request_var('host', ''), + request_var('username', ''), + htmlspecialchars_decode($request->untrimmed_variable('password', '')), + request_var('root_path', ''), + request_var('port', ''), + request_var('timeout', '') + ); $test_connection = $transfer->open_session(); // Make sure that the directory is correct by checking for the existence of common.php @@ -948,7 +955,14 @@ class install_update extends module } else { - $transfer = new $method(request_var('host', ''), request_var('username', ''), $request->untrimmed_variable('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); + $transfer = new $method( + request_var('host', ''), + request_var('username', ''), + htmlspecialchars_decode($request->untrimmed_variable('password', '')), + request_var('root_path', ''), + request_var('port', ''), + request_var('timeout', '') + ); $transfer->open_session(); } From 1e05fd4c627d23b7756796c5acac27d2562a8607 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 15:01:29 +0200 Subject: [PATCH 0774/1142] [ticket/8713] Trim password in auth_db to keep compatibility. PHPBB3-8713 --- phpBB/includes/auth/auth_db.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpBB/includes/auth/auth_db.php b/phpBB/includes/auth/auth_db.php index 76790e4dad..ac944532a5 100644 --- a/phpBB/includes/auth/auth_db.php +++ b/phpBB/includes/auth/auth_db.php @@ -41,6 +41,10 @@ function login_db($username, $password, $ip = '', $browser = '', $forwarded_for global $db, $config; global $request; + // Auth plugins get the password untrimmed. + // For compatibility we trim() here. + $password = trim($password); + // do not allow empty password if (!$password) { From 73a75fc3d387f8d923186c5c04b1ca7bc6cda4ef Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 8 Sep 2012 15:02:06 +0200 Subject: [PATCH 0775/1142] [ticket/8713] Revert changes to ucp_profile, ucp_register and acp_users. Currently only auth_db is supported there and the password needs to be trimmed for compatibility because user_password stores phpbb_hash(htmlspecialchars(trim($password))) Setting passwords for other auth modules is currently not supported. Once setting/changing passwords is supported by auth plugins, the untrimmed_variable() should be used here and the result should be passed to the auth plugin. PHPBB3-8713 --- phpBB/includes/acp/acp_users.php | 4 ++-- phpBB/includes/ucp/ucp_profile.php | 6 +++--- phpBB/includes/ucp/ucp_register.php | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 2905b84d57..985a12d9ce 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -770,8 +770,8 @@ class acp_users 'username' => utf8_normalize_nfc(request_var('user', $user_row['username'], true)), 'user_founder' => request_var('user_founder', ($user_row['user_type'] == USER_FOUNDER) ? 1 : 0), 'email' => strtolower(request_var('user_email', $user_row['user_email'])), - 'new_password' => $request->untrimmed_variable('new_password', '', true), - 'password_confirm' => $request->untrimmed_variable('password_confirm', '', true), + 'new_password' => $request->variable('new_password', '', true), + 'password_confirm' => $request->variable('password_confirm', '', true), ); // Validation data - we do not check the password complexity setting here diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index db1e3e4722..89bf20a30f 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -46,9 +46,9 @@ class ucp_profile $data = array( 'username' => utf8_normalize_nfc(request_var('username', $user->data['username'], true)), 'email' => strtolower(request_var('email', $user->data['user_email'])), - 'new_password' => $request->untrimmed_variable('new_password', '', true), - 'cur_password' => $request->untrimmed_variable('cur_password', '', true), - 'password_confirm' => $request->untrimmed_variable('password_confirm', '', true), + 'new_password' => $request->variable('new_password', '', true), + 'cur_password' => $request->variable('cur_password', '', true), + 'password_confirm' => $request->variable('password_confirm', '', true), ); add_form_key('ucp_reg_details'); diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 5ae92a5cea..c57aec00a0 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -170,8 +170,8 @@ class ucp_register $data = array( 'username' => utf8_normalize_nfc(request_var('username', '', true)), - 'new_password' => $request->untrimmed_variable('new_password', '', true), - 'password_confirm' => $request->untrimmed_variable('password_confirm', '', true), + 'new_password' => $request->variable('new_password', '', true), + 'password_confirm' => $request->variable('password_confirm', '', true), 'email' => strtolower(request_var('email', '')), 'lang' => basename(request_var('lang', $user->lang_name)), 'tz' => request_var('tz', $timezone), From 238fab3bb908013fb0d7c95278b0a2a3b7fa5bae Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sun, 9 Sep 2012 21:41:29 +0200 Subject: [PATCH 0776/1142] [ticket/8713] Update untrimmed_variable() doc block. PHPBB3-8713 --- phpBB/includes/request/request.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/request/request.php b/phpBB/includes/request/request.php index aa62c3b610..a06fc0d85d 100644 --- a/phpBB/includes/request/request.php +++ b/phpBB/includes/request/request.php @@ -204,9 +204,9 @@ class phpbb_request implements phpbb_request_interface } /** - * Get a variable, but without trimming strings - * Same functionality as variable(), except does not run trim() on strings - * All variables in GET or POST requests should be retrieved through this function to maximise security. + * Get a variable, but without trimming strings. + * Same functionality as variable(), except does not run trim() on strings. + * This method should be used when handling passwords. * * @param string|array $var_name The form variable's name from which data shall be retrieved. * If the value is an array this may be an array of indizes which will give From ce7cffcf9ebb21bccf1fae4da41a924708429e94 Mon Sep 17 00:00:00 2001 From: Fyorl Date: Tue, 11 Sep 2012 09:42:29 +0100 Subject: [PATCH 0777/1142] [ticket/11045] Removed file conflict tests for compress class PHPBB3-11045 --- tests/compress/compress_test.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/tests/compress/compress_test.php b/tests/compress/compress_test.php index ac8dd358d3..65094671e3 100644 --- a/tests/compress/compress_test.php +++ b/tests/compress/compress_test.php @@ -25,12 +25,6 @@ class phpbb_compress_test extends phpbb_test_case 'dir/subdir/4.txt', ); - protected $conflicts = array( - '1_1.txt', - '1_2.txt', - 'dir/2_1.txt', - ); - protected function setUp() { // Required for compress::add_file @@ -88,11 +82,6 @@ class phpbb_compress_test extends phpbb_test_case ); $compress->add_custom_file($this->path . 'dir/3.txt', 'dir/3.txt'); $compress->add_data(file_get_contents($this->path . 'dir/subdir/4.txt'), 'dir/subdir/4.txt'); - - // Add multiples of the same file to check conflicts are handled - $compress->add_file($this->path . '1.txt', $this->path); - $compress->add_file($this->path . '1.txt', $this->path); - $compress->add_file($this->path . 'dir/2.txt', $this->path); } protected function valid_extraction($extra = array()) @@ -152,7 +141,7 @@ class phpbb_compress_test extends phpbb_test_case $compress->mode = 'r'; $compress->open(); $compress->extract('tests/compress/' . self::EXTRACT_DIR); - $this->valid_extraction($this->conflicts); + $this->valid_extraction(); } /** @@ -168,6 +157,6 @@ class phpbb_compress_test extends phpbb_test_case $compress = new compress_zip('r', $zip); $compress->extract('tests/compress/' . self::EXTRACT_DIR); - $this->valid_extraction($this->conflicts); + $this->valid_extraction(); } } From 503989979a2e6a7fb9d64ed249b09a0bef2b2f95 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 13 Sep 2012 16:44:26 -0400 Subject: [PATCH 0778/1142] [ticket/11086] Fix database_update.php to use Dependency Injection Container PHPBB3-11086 --- phpBB/install/database_update.php | 45 ++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 0b470ced26..323ba0c876 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -7,6 +7,10 @@ * */ +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + define('UPDATES_TO_VERSION', '3.1.0-dev'); // Enter any version to update from to test updates. The version within the db will not be updated. @@ -107,21 +111,38 @@ if (!defined('EXT_TABLE')) define('EXT_TABLE', $table_prefix . 'ext'); } -$phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', $phpbb_root_path . 'ext/', ".$phpEx"); -$phpbb_class_loader_ext->register(); -$phpbb_class_loader = new phpbb_class_loader('phpbb_', $phpbb_root_path . 'includes/', ".$phpEx"); -$phpbb_class_loader->register(); +$phpbb_container = new ContainerBuilder(); +$loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__.'/../config')); +$loader->load('services.yml'); + +// We must include the DI processor class files because the class loader +// is not yet set up +require($phpbb_root_path . 'includes/di/processor/interface.' . $phpEx); +require($phpbb_root_path . 'includes/di/processor/config.' . $phpEx); + +$processor = new phpbb_di_processor_config($phpbb_root_path . 'config.' . $phpEx, $phpbb_root_path, $phpEx); +$processor->process($phpbb_container); + +// Setup class loader first +$phpbb_class_loader = $phpbb_container->get('class_loader'); +$phpbb_class_loader_ext = $phpbb_container->get('class_loader.ext'); + +$ids = array_keys($phpbb_container->findTaggedServiceIds('container.processor')); +foreach ($ids as $id) +{ + $processor = $phpbb_container->get($id); + $processor->process($phpbb_container); +} // set up caching -$cache_factory = new phpbb_cache_factory($acm_type); -$cache = $cache_factory->get_service(); -$phpbb_class_loader_ext->set_cache($cache->get_driver()); -$phpbb_class_loader->set_cache($cache->get_driver()); +$cache = $phpbb_container->get('cache'); -$phpbb_dispatcher = new phpbb_event_dispatcher(); -$request = new phpbb_request(); -$user = new phpbb_user(); -$db = new $sql_db(); +// Instantiate some basic classes +$phpbb_dispatcher = $phpbb_container->get('dispatcher'); +$request = $phpbb_container->get('request'); +$user = $phpbb_container->get('user'); +$auth = $phpbb_container->get('auth'); +$db = $phpbb_container->get('dbal.conn'); // make sure request_var uses this request instance request_var('', 0, false, false, $request); // "dependency injection" for a function From 298fa894e7ea0807a295cd0ed55cabef8a27a3f3 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 13 Sep 2012 16:56:09 -0400 Subject: [PATCH 0779/1142] [ticket/11086] Move DI processing below $request definition As per PR #991 (ticket/11101). PHPBB3-11086 --- phpBB/install/database_update.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 323ba0c876..5fa6166913 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -119,7 +119,6 @@ $loader->load('services.yml'); // is not yet set up require($phpbb_root_path . 'includes/di/processor/interface.' . $phpEx); require($phpbb_root_path . 'includes/di/processor/config.' . $phpEx); - $processor = new phpbb_di_processor_config($phpbb_root_path . 'config.' . $phpEx, $phpbb_root_path, $phpEx); $processor->process($phpbb_container); @@ -127,13 +126,6 @@ $processor->process($phpbb_container); $phpbb_class_loader = $phpbb_container->get('class_loader'); $phpbb_class_loader_ext = $phpbb_container->get('class_loader.ext'); -$ids = array_keys($phpbb_container->findTaggedServiceIds('container.processor')); -foreach ($ids as $id) -{ - $processor = $phpbb_container->get($id); - $processor->process($phpbb_container); -} - // set up caching $cache = $phpbb_container->get('cache'); @@ -144,6 +136,13 @@ $user = $phpbb_container->get('user'); $auth = $phpbb_container->get('auth'); $db = $phpbb_container->get('dbal.conn'); +$ids = array_keys($phpbb_container->findTaggedServiceIds('container.processor')); +foreach ($ids as $id) +{ + $processor = $phpbb_container->get($id); + $processor->process($phpbb_container); +} + // make sure request_var uses this request instance request_var('', 0, false, false, $request); // "dependency injection" for a function From b24ee89cfc22fad322dcfc577d1c7d50bbd57809 Mon Sep 17 00:00:00 2001 From: Fyorl Date: Tue, 11 Sep 2012 09:44:13 +0100 Subject: [PATCH 0780/1142] [ticket/11109] Re-add file conflict checks to compress class PHPBB3-11109 --- tests/compress/compress_test.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/compress/compress_test.php b/tests/compress/compress_test.php index 65094671e3..ac8dd358d3 100644 --- a/tests/compress/compress_test.php +++ b/tests/compress/compress_test.php @@ -25,6 +25,12 @@ class phpbb_compress_test extends phpbb_test_case 'dir/subdir/4.txt', ); + protected $conflicts = array( + '1_1.txt', + '1_2.txt', + 'dir/2_1.txt', + ); + protected function setUp() { // Required for compress::add_file @@ -82,6 +88,11 @@ class phpbb_compress_test extends phpbb_test_case ); $compress->add_custom_file($this->path . 'dir/3.txt', 'dir/3.txt'); $compress->add_data(file_get_contents($this->path . 'dir/subdir/4.txt'), 'dir/subdir/4.txt'); + + // Add multiples of the same file to check conflicts are handled + $compress->add_file($this->path . '1.txt', $this->path); + $compress->add_file($this->path . '1.txt', $this->path); + $compress->add_file($this->path . 'dir/2.txt', $this->path); } protected function valid_extraction($extra = array()) @@ -141,7 +152,7 @@ class phpbb_compress_test extends phpbb_test_case $compress->mode = 'r'; $compress->open(); $compress->extract('tests/compress/' . self::EXTRACT_DIR); - $this->valid_extraction(); + $this->valid_extraction($this->conflicts); } /** @@ -157,6 +168,6 @@ class phpbb_compress_test extends phpbb_test_case $compress = new compress_zip('r', $zip); $compress->extract('tests/compress/' . self::EXTRACT_DIR); - $this->valid_extraction(); + $this->valid_extraction($this->conflicts); } } From 0c56bd45eff15b0bff2672ed08b390d0248625d8 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Mon, 23 Jul 2012 13:15:08 -0500 Subject: [PATCH 0781/1142] [ticket/11021] Better language strings for site home url/text Correct the logo title to be {L_HOME} if {U_HOME} is used. Check if the Home text is instead of just equal to false when outputting it to the template PHPBB3-11021 --- phpBB/includes/functions.php | 2 +- phpBB/language/en/acp/board.php | 10 +++++----- phpBB/styles/prosilver/template/overall_header.html | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index a8d3c25052..91e2b1b794 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -4935,7 +4935,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'L_LOGIN_LOGOUT' => $l_login_logout, 'L_INDEX' => $user->lang['FORUM_INDEX'], - 'L_HOME' => ($config['site_home_text']) ? $config['site_home_text'] : $user->lang['HOME'], + 'L_HOME' => (!empty($config['site_home_text'])) ? $config['site_home_text'] : $user->lang['HOME'], 'L_ONLINE_EXPLAIN' => $l_online_time, 'U_PRIVATEMSGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'), diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 6b043b887d..b80aec1f4a 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -45,14 +45,14 @@ $lang = array_merge($lang, array( 'DISABLE_BOARD' => 'Disable board', 'DISABLE_BOARD_EXPLAIN' => 'This will make the board unavailable to users. You can also enter a short (255 character) message to display if you wish.', 'DISPLAY_LAST_SUBJECT' => 'Display subject of last added post on forum list', - 'DISPLAY_LAST_SUBJECT_EXPLAIN' => 'The subject of the last added post will be displayed in the forum list with a hyperlink to the post. Subjects from password protected forums and forums in which user doesn’t have read access are not shown.', + 'DISPLAY_LAST_SUBJECT_EXPLAIN' => 'The subject of the last added post will be displayed in the forum list with a hyperlink to the post. Subjects from password protected forums and forums in which user doesn’t have read access are not shown.', 'OVERRIDE_STYLE' => 'Override user style', 'OVERRIDE_STYLE_EXPLAIN' => 'Replaces user’s style with the default.', 'SITE_DESC' => 'Site description', - 'SITE_HOME_TEXT' => 'Site home text', - 'SITE_HOME_TEXT_EXPLAIN' => 'Specify a Site Home Text to use your own instead of the default text in the breadcrumbs ’Home.’', - 'SITE_HOME_URL' => 'Site home URL', - 'SITE_HOME_URL_EXPLAIN' => 'If you specify a Site Home URL, a link to this page will be added to your board’s breadcrumbs and the site logo will also be linked to this address.', + 'SITE_HOME_TEXT' => 'Main website text', + 'SITE_HOME_TEXT_EXPLAIN' => 'This text will be displayed as a link to your website homepage in the board’s breadcrumbs. If not specified, it will default to ’Home’.', + 'SITE_HOME_URL' => 'Main website URL', + 'SITE_HOME_URL_EXPLAIN' => 'If specified, a link to this URL will be prepended to your board’s breadcrumbs. The board logo will also link to this URL.', 'SITE_NAME' => 'Site name', 'SYSTEM_TIMEZONE' => 'Guest timezone', 'SYSTEM_TIMEZONE_EXPLAIN' => 'Timezone to use for displaying times to users who are not logged in (guests, bots). Logged in users set their timezone during registration and can change it in their user control panel.', diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index 13907a6ae4..47a53c8f60 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -95,7 +95,7 @@
- +

{SITENAME}

{SITE_DESCRIPTION}

From 7a4701399412faedd725195f849c18c770a126cb Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Sat, 15 Sep 2012 10:44:18 -0500 Subject: [PATCH 0782/1142] [ticket/11021] Use L_SITE_HOME instead of L_HOME Check site home url against !== '', not empty PHPBB3-11021 --- phpBB/includes/functions.php | 2 +- phpBB/styles/prosilver/template/overall_footer.html | 4 ++-- phpBB/styles/prosilver/template/overall_header.html | 4 ++-- phpBB/styles/subsilver2/template/breadcrumbs.html | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 91e2b1b794..0aa3aa016e 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -4935,7 +4935,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'L_LOGIN_LOGOUT' => $l_login_logout, 'L_INDEX' => $user->lang['FORUM_INDEX'], - 'L_HOME' => (!empty($config['site_home_text'])) ? $config['site_home_text'] : $user->lang['HOME'], + 'L_SITE_HOME' => ($config['site_home_text'] !== '') ? $config['site_home_text'] : $user->lang['HOME'], 'L_ONLINE_EXPLAIN' => $l_online_time, 'U_PRIVATEMSGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'), diff --git a/phpBB/styles/prosilver/template/overall_footer.html b/phpBB/styles/prosilver/template/overall_footer.html index ed05bbe80f..30912b6637 100644 --- a/phpBB/styles/prosilver/template/overall_footer.html +++ b/phpBB/styles/prosilver/template/overall_footer.html @@ -4,9 +4,9 @@ diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php index 7d6fed6164..960cc5b335 100644 --- a/phpBB/install/convertors/convert_phpbb20.php +++ b/phpBB/install/convertors/convert_phpbb20.php @@ -33,7 +33,7 @@ $convertor_data = array( 'forum_name' => 'phpBB 2.0.x', 'version' => '1.0.3', 'phpbb_version' => '3.0.11', - 'author' => 'phpBB Group', + 'author' => 'phpBB Group', 'dbms' => $dbms, 'dbhost' => $dbhost, 'dbport' => $dbport, diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 5a186d0eeb..40837145ba 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -527,7 +527,7 @@ function _print_footer()
diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 5135e2dbd8..ad46e273c2 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -569,7 +569,7 @@ class module echo '
'; echo '
'; echo ' '; echo ''; echo ''; diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html b/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html index d92abb06dd..67e14defc3 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html @@ -48,7 +48,7 @@ diff --git a/phpBB/styles/prosilver/template/viewtopic_print.html b/phpBB/styles/prosilver/template/viewtopic_print.html index 0fd0e6dfa6..39d2d76394 100644 --- a/phpBB/styles/prosilver/template/viewtopic_print.html +++ b/phpBB/styles/prosilver/template/viewtopic_print.html @@ -44,7 +44,7 @@ diff --git a/phpBB/styles/prosilver/theme/stylesheet.css b/phpBB/styles/prosilver/theme/stylesheet.css index 4a7356fbaa..40620179a1 100644 --- a/phpBB/styles/prosilver/theme/stylesheet.css +++ b/phpBB/styles/prosilver/theme/stylesheet.css @@ -3,7 +3,7 @@ Style name: prosilver (the default phpBB 3.0.x style) Based on style: Original author: Tom Beddard ( http://www.subblue.com/ ) - Modified by: phpBB Group ( http://www.phpbb.com/ ) + Modified by: phpBB Group ( https://www.phpbb.com/ ) -------------------------------------------------------------- */ diff --git a/phpBB/styles/subsilver2/template/ucp_pm_viewmessage_print.html b/phpBB/styles/subsilver2/template/ucp_pm_viewmessage_print.html index 24194e4c26..cd55c16a50 100644 --- a/phpBB/styles/subsilver2/template/ucp_pm_viewmessage_print.html +++ b/phpBB/styles/subsilver2/template/ucp_pm_viewmessage_print.html @@ -114,7 +114,7 @@ hr.sep { {S_TIMEZONE} - Powered by phpBB® Forum Software © phpBB Group
http://www.phpbb.com/
+ Powered by phpBB® Forum Software © phpBB Group
https://www.phpbb.com/
diff --git a/phpBB/styles/subsilver2/template/viewtopic_print.html b/phpBB/styles/subsilver2/template/viewtopic_print.html index 964c95f677..1ca1eccc99 100644 --- a/phpBB/styles/subsilver2/template/viewtopic_print.html +++ b/phpBB/styles/subsilver2/template/viewtopic_print.html @@ -128,7 +128,7 @@ hr.sep { {S_TIMEZONE} - Powered by phpBB® Forum Software © phpBB Group
http://www.phpbb.com/
+ Powered by phpBB® Forum Software © phpBB Group
https://www.phpbb.com/
diff --git a/phpBB/styles/subsilver2/theme/stylesheet.css b/phpBB/styles/subsilver2/theme/stylesheet.css index 444104ae3c..177a988e93 100644 --- a/phpBB/styles/subsilver2/theme/stylesheet.css +++ b/phpBB/styles/subsilver2/theme/stylesheet.css @@ -3,7 +3,7 @@ Style name: subsilver2 Based on style: subSilver (the default phpBB 2.0.x style) Original author: Tom Beddard ( http://www.subblue.com/ ) - Modified by: phpBB Group ( http://www.phpbb.com/ ) + Modified by: phpBB Group ( https://www.phpbb.com/ ) -------------------------------------------------------------- */ From 305abfde963e764d5e6be0c7b1c1b9496a2477b2 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 8 Oct 2012 10:58:04 +0530 Subject: [PATCH 0793/1142] [ticket/11051] fix spaces PHPBB3-11051 --- phpBB/includes/search/fulltext_native.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/search/fulltext_native.php b/phpBB/includes/search/fulltext_native.php index 1100484ebd..bbc2236b3c 100644 --- a/phpBB/includes/search/fulltext_native.php +++ b/phpBB/includes/search/fulltext_native.php @@ -23,9 +23,9 @@ if (!defined('IN_PHPBB')) class phpbb_search_fulltext_native extends phpbb_search_base { protected $stats = array(); - protected $word_length = array(); - protected $search_query; - protected $common_words = array(); + protected $word_length = array(); + protected $search_query; + protected $common_words = array(); protected $must_contain_ids = array(); protected $must_not_contain_ids = array(); From 5db30e66fd03dacb4bb961afd9672eb9d65ba148 Mon Sep 17 00:00:00 2001 From: Vinny Date: Tue, 9 Oct 2012 00:31:59 -0300 Subject: [PATCH 0794/1142] [ticket/11139] Fix fatal error on colour swatch window PHPBB3-11139 --- phpBB/adm/swatch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/adm/swatch.php b/phpBB/adm/swatch.php index 434b00e542..5e8984db70 100644 --- a/phpBB/adm/swatch.php +++ b/phpBB/adm/swatch.php @@ -22,7 +22,7 @@ $auth->acl($user->data); $user->setup(); // Set custom template for admin area -$template->set_custom_template($phpbb_root_path . 'adm/style', 'admin'); +$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', ''); $template->set_filenames(array( 'body' => 'colour_swatch.html') From cf810fb775407d6e6e5aa62a072b141bdb61e3c9 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Tue, 9 Oct 2012 22:00:28 -0500 Subject: [PATCH 0795/1142] [ticket/11140] Fix an error from an incorrect variable name PHPBB3-11140 --- phpBB/includes/mcp/mcp_front.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/mcp/mcp_front.php b/phpBB/includes/mcp/mcp_front.php index 13398e62bc..ba4b15895a 100644 --- a/phpBB/includes/mcp/mcp_front.php +++ b/phpBB/includes/mcp/mcp_front.php @@ -251,7 +251,7 @@ function mcp_front_view($id, $mode, $action) 'ORDER_BY' => 'p.message_time DESC', ); - $sql_ary = $db->sql_build_query('SELECT', $sql_ary); + $sql = $db->sql_build_query('SELECT', $sql_ary); $result = $db->sql_query_limit($sql, 5); $pm_by_id = $pm_list = array(); From d434672dde1592f51013459357c25ed49887d12f Mon Sep 17 00:00:00 2001 From: Senky Date: Mon, 23 Jul 2012 14:28:47 +0200 Subject: [PATCH 0796/1142] [ticket/10967] adding $root_path to posting_get_topic_icons PHPBB3-10967 --- phpBB/includes/functions_posting.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 68b6199cf5..aa15593a19 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -288,13 +288,15 @@ function posting_gen_topic_icons($mode, $icon_id) if (sizeof($icons)) { + $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $phpbb_root_path; + foreach ($icons as $id => $data) { if ($data['display']) { $template->assign_block_vars('topic_icon', array( 'ICON_ID' => $id, - 'ICON_IMG' => $phpbb_root_path . $config['icons_path'] . '/' . $data['img'], + 'ICON_IMG' => $root_path . $config['icons_path'] . '/' . $data['img'], 'ICON_WIDTH' => $data['width'], 'ICON_HEIGHT' => $data['height'], @@ -2637,7 +2639,7 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u * - 'topic_last_post_subject' * - 'topic_last_poster_name' * - 'topic_last_poster_colour' -* @param int $bump_time The time at which topic was bumped, usually it is a current time as obtained via time(). +* @param int $bump_time The time at which topic was bumped, usually it is a current time as obtained via time(). * @return string An URL to the bumped topic, example: ./viewtopic.php?forum_id=1&topic_id=2&p=3#p3 */ function phpbb_bump_topic($forum_id, $topic_id, $post_data, $bump_time = false) From 571d352eed670ad986bd653a2ac0bd81429c9cf5 Mon Sep 17 00:00:00 2001 From: Vinny Date: Tue, 9 Oct 2012 14:29:26 -0300 Subject: [PATCH 0797/1142] [ticket/11139] Adding the $phpbb_admin_path variable PHPBB3-11139 --- phpBB/adm/swatch.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpBB/adm/swatch.php b/phpBB/adm/swatch.php index 5e8984db70..86498a255f 100644 --- a/phpBB/adm/swatch.php +++ b/phpBB/adm/swatch.php @@ -21,6 +21,8 @@ $user->session_begin(false); $auth->acl($user->data); $user->setup(); +$phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : './'; + // Set custom template for admin area $phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', ''); From d376348acf271ab0e2bcba4e8eb5993192d0ef87 Mon Sep 17 00:00:00 2001 From: westr Date: Tue, 16 Oct 2012 12:44:33 +0100 Subject: [PATCH 0798/1142] [ticket/11093] acp_users_overview.html has a wrongly placed Amended the closing dd tag to the appropriate line: line 145 instead of 141 PHPBB3-11093 --- phpBB/adm/style/acp_users_overview.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpBB/adm/style/acp_users_overview.html b/phpBB/adm/style/acp_users_overview.html index e2dcdb6307..ba350a13fb 100644 --- a/phpBB/adm/style/acp_users_overview.html +++ b/phpBB/adm/style/acp_users_overview.html @@ -142,10 +142,11 @@

{L_DELETE_USER_EXPLAIN}
-
+ {L_USER_NO_POSTS_TO_DELETE} +

From c630480ca1a426cb0897be35626baac2694fccf5 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 17 Oct 2012 15:03:06 -0400 Subject: [PATCH 0799/1142] [ticket/10848] Redirect from adm to installer correctly. PHPBB3-10848 --- phpBB/common.php | 6 +++- phpBB/includes/functions.php | 30 ++++++++++++++++++++ tests/functions/clean_path_test.php | 44 +++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/functions/clean_path_test.php diff --git a/phpBB/common.php b/phpBB/common.php index 491addc5e0..bdb33707cc 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -38,10 +38,14 @@ if (!defined('PHPBB_INSTALLED')) $script_name = (!empty($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : getenv('REQUEST_URI'); } + // $phpbb_root_path accounts for redirects from e.g. /adm + $script_path = trim(dirname($script_name)) . '/' . $phpbb_root_path . 'install/index.' . $phpEx; // Replace any number of consecutive backslashes and/or slashes with a single slash // (could happen on some proxy setups and/or Windows servers) - $script_path = trim(dirname($script_name)) . '/install/index.' . $phpEx; $script_path = preg_replace('#[\\\\/]{2,}#', '/', $script_path); + // Eliminate . and .. from the path + require($phpbb_root_path . 'includes/functions.' . $phpEx); + $script_path = clean_path($script_path); $url = (($secure) ? 'https://' : 'http://') . $server_name; diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index ca58220619..2391b45038 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1176,6 +1176,36 @@ else } } +/** +* Eliminates useless . and .. components from specified path. +* +* @param string $path Path to clean +* @return string Cleaned path +*/ +function clean_path($path) +{ + $exploded = explode('/', $path); + $filtered = array(); + foreach ($exploded as $part) + { + if ($part === '.' && !empty($filtered)) + { + continue; + } + + if ($part === '..' && !empty($filtered) && $filtered[sizeof($filtered) - 1] !== '..') + { + array_pop($filtered); + } + else + { + $filtered[] = $part; + } + } + $path = implode('/', $filtered); + return $path; +} + if (!function_exists('htmlspecialchars_decode')) { /** diff --git a/tests/functions/clean_path_test.php b/tests/functions/clean_path_test.php new file mode 100644 index 0000000000..4c8fe54909 --- /dev/null +++ b/tests/functions/clean_path_test.php @@ -0,0 +1,44 @@ +assertEquals($expected, $output); + } +} From bb09cd9c8e76ac3af848d09db8ea1928dab66158 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 17 Oct 2012 15:13:35 -0400 Subject: [PATCH 0800/1142] [ticket/10848] Add phpbb_ prefix. PHPBB3-10848 --- phpBB/common.php | 2 +- phpBB/includes/functions.php | 2 +- tests/functions/clean_path_test.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/common.php b/phpBB/common.php index bdb33707cc..5849d48453 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -45,7 +45,7 @@ if (!defined('PHPBB_INSTALLED')) $script_path = preg_replace('#[\\\\/]{2,}#', '/', $script_path); // Eliminate . and .. from the path require($phpbb_root_path . 'includes/functions.' . $phpEx); - $script_path = clean_path($script_path); + $script_path = phpbb_clean_path($script_path); $url = (($secure) ? 'https://' : 'http://') . $server_name; diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 2391b45038..65d8be32ad 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1182,7 +1182,7 @@ else * @param string $path Path to clean * @return string Cleaned path */ -function clean_path($path) +function phpbb_clean_path($path) { $exploded = explode('/', $path); $filtered = array(); diff --git a/tests/functions/clean_path_test.php b/tests/functions/clean_path_test.php index 4c8fe54909..bcbe9838d9 100644 --- a/tests/functions/clean_path_test.php +++ b/tests/functions/clean_path_test.php @@ -37,7 +37,7 @@ class phpbb_clean_path_test extends phpbb_test_case */ public function test_clean_path($input, $expected) { - $output = clean_path($input); + $output = phpbb_clean_path($input); $this->assertEquals($expected, $output); } From e76fd6a3959c632a2e012ce82568b9e04f87b8db Mon Sep 17 00:00:00 2001 From: Drae Date: Sun, 22 Jul 2012 16:24:07 +0100 Subject: [PATCH 0801/1142] [ticket/11018] Attempt to fix li.pagination alignment issue This is somewhat kludgy fix for the vertical alignment issue for pagination contained within a linklist parented li element. Tested and doesn't seem to impact anything else negatively. May need further browser testing. PHPBB3-11018 --- phpBB/styles/prosilver/theme/common.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 4b4fa263b1..ff46163f71 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -510,9 +510,16 @@ li.pagination { margin-bottom: 0; } +li.pagination ul { + margin-top: -2px; + vertical-align: middle; +} + .pagination ul li, dl .pagination ul li, dl.icon .pagination ul li { display: inline; padding: 0; + font-size: 100%; + line-height: normal; } .pagination li a, .pagnation li span, li .pagination li a, li .pagnation li span, .pagination li.active span, .pagination li.ellipsis span { From a51aa9b47c526d173386510f3f67303243be03fc Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 25 Aug 2012 13:09:22 +0200 Subject: [PATCH 0802/1142] [ticket/11018] Fix minor issues with CSS in prosilver PHPBB3-11018 --- phpBB/styles/prosilver/theme/colours.css | 6 +++--- phpBB/styles/prosilver/theme/common.css | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index 29968cbb14..d7ce9a7622 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -151,9 +151,9 @@ dl.details dd { border-color: #B4BAC0; } -.pagination li.ellipsis span { +.pagination li.ellipsis span { background-color: transparent; - color: #000 + color: #000000; } .pagination li.active span { @@ -165,7 +165,7 @@ dl.details dd { .pagination li a:hover, .pagination .active a:hover { border-color: #368AD2; background-color: #368AD2; - color: #FFF; + color: #FFFFFF; } .pagination li a:active, .pagination li.active a:active { diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index ff46163f71..50b22f44df 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -515,11 +515,11 @@ li.pagination ul { vertical-align: middle; } -.pagination ul li, dl .pagination ul li, dl.icon .pagination ul li { +.pagination ul li, dl .pagination ul li, dl.icon .pagination ul li { display: inline; padding: 0; font-size: 100%; - line-height: normal; + line-height: normal; } .pagination li a, .pagnation li span, li .pagination li a, li .pagnation li span, .pagination li.active span, .pagination li.ellipsis span { From 43a713ea8356221eb199c5aba066efa15013c186 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 25 Aug 2012 13:11:07 +0200 Subject: [PATCH 0803/1142] [ticket/11067] Copy prosilver CSS to adm, so the pagination looks the same PHPBB3-11067 PHPBB3-11018 --- phpBB/adm/style/admin.css | 100 +++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/phpBB/adm/style/admin.css b/phpBB/adm/style/admin.css index 585707600d..8551c952c7 100644 --- a/phpBB/adm/style/admin.css +++ b/phpBB/adm/style/admin.css @@ -1148,55 +1148,79 @@ input.disabled { width: auto; text-align: right; margin-top: 5px; - font-size: 0.85em; - padding-bottom: 2px; + float: right; } .rtl .pagination { text-align: left; + float: left; } -.pagination strong, -.pagination b { - font-weight: normal; -} - -.pagination span.page-sep { - display:none; -} - -.pagination span strong { - padding: 0 2px; - margin: 0 2px; - font-weight: normal; - font-size: 0.85em; - color: #FFFFFF; - background: #4692BF; - border: 1px solid #4692BF; -} - -.pagination span a, .pagination span a:link, .pagination span a:visited, .pagination span a:active { - font-weight: normal; - font-size: 0.85em; - text-decoration: none; - color: #5C758C; - margin: 0 2px; - padding: 0 2px; - background: #ECEDEE; - border: 1px solid #B4BAC0; -} - -.pagination span a:hover { - border-color: #368AD2; - background: #368AD2; - color: #FFFFFF; - text-decoration: none; +li.pagination { + margin-top: 0; } .pagination img { vertical-align: middle; } +.pagination ul { + display: inline-block; + *display: inline; /* IE7 inline-block hack */ + *zoom: 1; + margin-left: 0; + margin-bottom: 0; +} + +li.pagination ul { + margin-top: -2px; + vertical-align: middle; +} + +.pagination ul li, dl .pagination ul li, dl.icon .pagination ul li { + display: inline; + padding: 0; + font-size: 100%; + line-height: normal; +} + +.pagination li a, .pagnation li span, li .pagination li a, li .pagnation li span, .pagination li.active span, .pagination li.ellipsis span { + font-weight: normal; + text-decoration: none; + padding: 0 2px; + border: 1px solid transparent; + font-size: 0.9em; + line-height: 1.5em; +} + +.pagination li a, .pagination li a:link, .pagination li a:visited { + color: #5C758C; + background-color: #ECEDEE; + border-color: #B4BAC0; +} + +.pagination li.ellipsis span { + background-color: transparent; + color: #000000; +} + +.pagination li.active span { + color: #FFFFFF; + background-color: #4692BF; + border-color: #4692BF; +} + +.pagination li a:hover, .pagination .active a:hover { + color: #FFFFFF; + background-color: #368AD2; + border-color: #368AD2; +} + +.pagination li a:active, .pagination li.active a:active { + color: #5C758C; + background-color: #ECEDEE; + border-color: #B4BAC0; +} /* Action Highlighting ---------------------------------------- */ @@ -1727,4 +1751,4 @@ fieldset.permissions .padding { .requirements_not_met dt label, .requirements_not_met dd p { color: #FFFFFF; font-size: 1.4em; -} \ No newline at end of file +} From fa5753de707e0b24c686cf75a7ae9d261bc2a8f2 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 25 Aug 2012 13:20:45 +0200 Subject: [PATCH 0804/1142] [ticket/11018] Swap prev/next links on pagination to the old order In the old pagination Prev was left of the pagination and Next right of the pagination. While moving these blocks, I also removed the whitespaces, which were introduced. PHPBB3-11023 PHPBB3-11018 --- phpBB/includes/functions.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 0c9421c12f..08dd03504c 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2112,14 +2112,14 @@ function phpbb_generate_template_pagination($template, $base_url, $block_var_nam $end_page = ($total_pages > 5) ? max(min($total_pages, $on_page + 3), 5) : $total_pages; } - if ($on_page != $total_pages) + if ($on_page != 1) { $template->assign_block_vars($block_var_name, array( 'PAGE_NUMBER' => '', - 'PAGE_URL' => $base_url . $url_delim . $start_name . '=' . ($on_page * $per_page), + 'PAGE_URL' => $base_url . $url_delim . $start_name . '=' . (($on_page - 2) * $per_page), 'S_IS_CURRENT' => false, - 'S_IS_PREV' => false, - 'S_IS_NEXT' => true, + 'S_IS_PREV' => true, + 'S_IS_NEXT' => false, 'S_IS_ELLIPSIS' => false, )); } @@ -2166,14 +2166,14 @@ function phpbb_generate_template_pagination($template, $base_url, $block_var_nam } while ($at_page <= $total_pages); - if ($on_page != 1) + if ($on_page != $total_pages) { $template->assign_block_vars($block_var_name, array( 'PAGE_NUMBER' => '', - 'PAGE_URL' => $base_url . $url_delim . $start_name . '=' . (($on_page - 2) * $per_page), + 'PAGE_URL' => $base_url . $url_delim . $start_name . '=' . ($on_page * $per_page), 'S_IS_CURRENT' => false, - 'S_IS_PREV' => true, - 'S_IS_NEXT' => false, + 'S_IS_PREV' => false, + 'S_IS_NEXT' => true, 'S_IS_ELLIPSIS' => false, )); } From 5ea662f649833f50483da544b513ca102e390fd8 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 25 Aug 2012 14:34:48 +0200 Subject: [PATCH 0805/1142] [ticket/11014] Restore template vars for next/previous links They were dropped while the function was refactored: If the block_var_name is a nested block, we will use the last (most inner) block as a prefix for the template variables. If the last block name is pagination, the prefix is empty. If the rest of the block_var_name is not empty, we will modify the last row of that block and add our pagination items. PHPBB3-11014 --- phpBB/includes/functions.php | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 08dd03504c..4e5be20dbf 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2177,6 +2177,45 @@ function phpbb_generate_template_pagination($template, $base_url, $block_var_nam 'S_IS_ELLIPSIS' => false, )); } + + // If the block_var_name is a nested block, we will use the last (most + // inner) block as a prefix for the template variables. If the last block + // name is pagination, the prefix is empty. If the rest of the + // block_var_name is not empty, we will modify the last row of that block + // and add our pagination items. + $tpl_block_name = $tpl_prefix = ''; + if (strrpos($block_var_name, '.') !== false) + { + $tpl_block_name = substr($block_var_name, 0, strrpos($block_var_name, '.')); + $tpl_prefix = strtoupper(substr($block_var_name, strrpos($block_var_name, '.') + 1)); + } + else + { + $tpl_prefix = strtoupper($block_var_name); + } + $tpl_prefix = ($tpl_prefix == 'PAGINATION') ? '' : $tpl_prefix . '_'; + + $previous_page = ($on_page != 1) ? $base_url . $url_delim . $start_name . '=' . (($on_page - 2) * $per_page) : ''; + + $template_array = array( + $tpl_prefix . 'BASE_URL' => $base_url, + 'A_' . $tpl_prefix . 'BASE_URL' => addslashes($base_url), + $tpl_prefix . 'PER_PAGE' => $per_page, + $tpl_prefix . 'PREVIOUS_PAGE' => $previous_page, + $tpl_prefix . 'PREV_PAGE' => $previous_page, + $tpl_prefix . 'NEXT_PAGE' => ($on_page != $total_pages) ? $base_url . $url_delim . $start_name . '=' . ($on_page * $per_page) : '', + $tpl_prefix . 'TOTAL_PAGES' => $total_pages, + $tpl_prefix . 'CURRENT_PAGE' => $on_page, + ); + + if ($tpl_block_name) + { + $template->alter_block_array($tpl_block_name, $template_array, true, 'change'); + } + else + { + $template->assign_vars($template_array); + } } /** From ada2d4c91bb063f3d9d591bfc3ce9552957772e0 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 25 Aug 2012 14:35:57 +0200 Subject: [PATCH 0806/1142] [ticket/11018] Always display previous/next links if we can display one PHPBB3-11018 --- phpBB/styles/prosilver/template/viewtopic_body.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index 4534dc5bcc..01b6a504a2 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -245,7 +245,7 @@ - +

From ceb5a40eecbc60577ce0735254a4a189d719302e Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 25 Aug 2012 14:53:21 +0200 Subject: [PATCH 0807/1142] [ticket/11023] Fix additional whitespaces that were added by PHPBB3-10968 PHPBB3-11023 --- phpBB/includes/acp/acp_attachments.php | 4 ++-- phpBB/includes/acp/acp_groups.php | 4 ++-- phpBB/includes/acp/acp_icons.php | 4 ++-- phpBB/includes/acp/acp_inactive.php | 5 ++--- phpBB/includes/acp/acp_logs.php | 2 +- phpBB/includes/acp/acp_users.php | 4 ++-- phpBB/includes/functions.php | 1 - phpBB/includes/mcp/mcp_forum.php | 2 +- phpBB/includes/mcp/mcp_logs.php | 4 ++-- phpBB/includes/mcp/mcp_notes.php | 2 +- phpBB/includes/mcp/mcp_pm_reports.php | 4 ++-- phpBB/includes/mcp/mcp_queue.php | 2 +- phpBB/includes/mcp/mcp_reports.php | 2 +- phpBB/includes/mcp/mcp_topic.php | 2 +- phpBB/includes/mcp/mcp_warn.php | 2 +- phpBB/includes/ucp/ucp_attachments.php | 4 ++-- phpBB/includes/ucp/ucp_pm_viewfolder.php | 2 +- phpBB/memberlist.php | 2 +- phpBB/search.php | 4 ++-- 19 files changed, 27 insertions(+), 29 deletions(-) diff --git a/phpBB/includes/acp/acp_attachments.php b/phpBB/includes/acp/acp_attachments.php index eccc935a6e..9d6c2d5de1 100644 --- a/phpBB/includes/acp/acp_attachments.php +++ b/phpBB/includes/acp/acp_attachments.php @@ -1163,7 +1163,7 @@ class acp_attachments $template->assign_vars(array( 'S_ACTION_OPTIONS' => ($auth->acl_get('a_board')) ? true : false, 'U_ACTION' => $this->u_action,) - ); + ); } // Make sure $start is set to the last page if it exceeds the amount @@ -1224,7 +1224,7 @@ class acp_attachments $base_url = $this->u_action . "&$u_sort_param"; phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $num_files, $attachments_per_page, $start); - + $template->assign_vars(array( 'TOTAL_FILES' => $num_files, 'TOTAL_SIZE' => get_formatted_filesize($total_size), diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php index f88fa76df1..9621407211 100644 --- a/phpBB/includes/acp/acp_groups.php +++ b/phpBB/includes/acp/acp_groups.php @@ -683,8 +683,8 @@ class acp_groups } $base_url = $this->u_action . "&action=$action&g=$group_id"; - phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total_members, $config['topics_per_page'], $start); - + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total_members, $config['topics_per_page'], $start); + $template->assign_vars(array( 'S_LIST' => true, 'S_GROUP_SPECIAL' => ($group_row['group_type'] == GROUP_SPECIAL) ? true : false, diff --git a/phpBB/includes/acp/acp_icons.php b/phpBB/includes/acp/acp_icons.php index b7be92d477..db4b4263b0 100644 --- a/phpBB/includes/acp/acp_icons.php +++ b/phpBB/includes/acp/acp_icons.php @@ -927,8 +927,8 @@ class acp_icons } } $db->sql_freeresult($result); - - phpbb_generate_template_pagination($template, $this->u_action, 'pagination', 'start', $item_count, $config['smilies_per_page'], $pagination_start); + + phpbb_generate_template_pagination($template, $this->u_action, 'pagination', 'start', $item_count, $config['smilies_per_page'], $pagination_start); } /** diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index 1e23c2e6cf..bf7a9e11e4 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -289,8 +289,8 @@ class acp_inactive } $base_url = $this->u_action . "&$u_sort_param&users_per_page=$per_page"; - phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $inactive_count, $per_page, $start); - + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $inactive_count, $per_page, $start); + $template->assign_vars(array( 'S_INACTIVE_USERS' => true, 'S_INACTIVE_OPTIONS' => build_select($option_ary), @@ -299,7 +299,6 @@ class acp_inactive 'S_SORT_KEY' => $s_sort_key, 'S_SORT_DIR' => $s_sort_dir, 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $inactive_count, $per_page, $start), - 'USERS_PER_PAGE' => $per_page, 'U_ACTION' => $this->u_action . "&$u_sort_param&users_per_page=$per_page&start=$start", diff --git a/phpBB/includes/acp/acp_logs.php b/phpBB/includes/acp/acp_logs.php index 4538633d6c..d86521532c 100644 --- a/phpBB/includes/acp/acp_logs.php +++ b/phpBB/includes/acp/acp_logs.php @@ -131,7 +131,7 @@ class acp_logs $base_url = $this->u_action . "&$u_sort_param$keywords_param"; phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); - + $template->assign_vars(array( 'L_TITLE' => $l_title, 'L_EXPLAIN' => $l_title_explain, diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 985a12d9ce..82d8ef5cbb 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1159,7 +1159,7 @@ class acp_users $base_url = $this->u_action . "&u=$user_id&$u_sort_param"; phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); - + $template->assign_vars(array( 'S_FEEDBACK' => true, 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $log_count, $config['topics_per_page'], $start), @@ -2075,7 +2075,7 @@ class acp_users $base_url = $this->u_action . "&u=$user_id&sk=$sort_key&sd=$sort_dir"; phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $num_attachments, $config['topics_per_page'], $start); - + $template->assign_vars(array( 'S_ATTACHMENTS' => true, 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $num_attachments, $config['topics_per_page'], $start), diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 4e5be20dbf..2e42dfe94e 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2240,7 +2240,6 @@ function phpbb_on_page($template, $user, $base_url, $num_items, $per_page, $star $template->assign_vars(array( 'PER_PAGE' => $per_page, 'ON_PAGE' => $on_page, - 'A_BASE_URL' => addslashes($base_url), )); diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index 7b3bc82093..151677bcfe 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -103,7 +103,7 @@ function mcp_forum_view($id, $mode, $action, $forum_info) $base_url = $url . "&i=$id&action=$action&mode=$mode&sd=$sort_dir&sk=$sort_key&st=$sort_days" . (($merge_select) ? $selected_ids : ''); phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $forum_topics, $topics_per_page, $start); - + $template->assign_vars(array( 'ACTION' => $action, 'FORUM_NAME' => $forum_info['forum_name'], diff --git a/phpBB/includes/mcp/mcp_logs.php b/phpBB/includes/mcp/mcp_logs.php index c1724b20d9..f706840492 100644 --- a/phpBB/includes/mcp/mcp_logs.php +++ b/phpBB/includes/mcp/mcp_logs.php @@ -172,8 +172,8 @@ class mcp_logs $start = view_log('mod', $log_data, $log_count, $config['topics_per_page'], $start, $forum_list, $topic_id, 0, $sql_where, $sql_sort, $keywords); $base_url = $this->u_action . "&$u_sort_param$keywords_param"; - phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); - + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $log_count, $config['topics_per_page'], $start), 'TOTAL' => $user->lang('TOTAL_LOGS', (int) $log_count), diff --git a/phpBB/includes/mcp/mcp_notes.php b/phpBB/includes/mcp/mcp_notes.php index bbf618ebef..59cdf3c27e 100644 --- a/phpBB/includes/mcp/mcp_notes.php +++ b/phpBB/includes/mcp/mcp_notes.php @@ -217,7 +217,7 @@ class mcp_notes $base_url = $this->u_action . "&$u_sort_param$keywords_param"; phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); - + $template->assign_vars(array( 'U_POST_ACTION' => $this->u_action, 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php index 24e531517c..be18dba944 100644 --- a/phpBB/includes/mcp/mcp_pm_reports.php +++ b/phpBB/includes/mcp/mcp_pm_reports.php @@ -297,10 +297,10 @@ class mcp_pm_reports } } } - + $base_url = $this->u_action . "&st=$sort_days&sk=$sort_key&sd=$sort_dir"; phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $config['topics_per_page'], $start); - + // Now display the page $template->assign_vars(array( 'L_EXPLAIN' => ($mode == 'pm_reports') ? $user->lang['MCP_PM_REPORTS_OPEN_EXPLAIN'] : $user->lang['MCP_PM_REPORTS_CLOSED_EXPLAIN'], diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index b44685b8a3..0b195aa9d8 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -421,7 +421,7 @@ class mcp_queue $base_url = $this->u_action . "&f=$forum_id&st=$sort_days&sk=$sort_key&sd=$sort_dir"; phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $config['topics_per_page'], $start); - + // Now display the page $template->assign_vars(array( 'L_DISPLAY_ITEMS' => ($mode == 'unapproved_posts') ? $user->lang['DISPLAY_POSTS'] : $user->lang['DISPLAY_TOPICS'], diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 2890cd56e2..f2c5080df5 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -413,7 +413,7 @@ class mcp_reports $base_url = $this->u_action . "&f=$forum_id&t=$topic_id&st=$sort_days&sk=$sort_key&sd=$sort_dir"; phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $config['topics_per_page'], $start); - + // Now display the page $template->assign_vars(array( 'L_EXPLAIN' => ($mode == 'reports') ? $user->lang['MCP_REPORTS_OPEN_EXPLAIN'] : $user->lang['MCP_REPORTS_CLOSED_EXPLAIN'], diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index e39e553ab6..62483270c0 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -311,7 +311,7 @@ function mcp_topic_view($id, $mode, $action) { phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $posts_per_page, $start); } - + $template->assign_vars(array( 'TOPIC_TITLE' => $topic_info['topic_title'], 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $topic_info['forum_id'] . '&t=' . $topic_info['topic_id']), diff --git a/phpBB/includes/mcp/mcp_warn.php b/phpBB/includes/mcp/mcp_warn.php index aefddb7c01..6a8fb4c5d5 100644 --- a/phpBB/includes/mcp/mcp_warn.php +++ b/phpBB/includes/mcp/mcp_warn.php @@ -177,7 +177,7 @@ class mcp_warn $base_url = append_sid("{$phpbb_root_path}mcp.$phpEx", "i=warn&mode=list&st=$st&sk=$sk&sd=$sd"); phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $user_count, $config['topics_per_page'], $start); - + $template->assign_vars(array( 'U_POST_ACTION' => $this->u_action, 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, diff --git a/phpBB/includes/ucp/ucp_attachments.php b/phpBB/includes/ucp/ucp_attachments.php index e4c351709b..dc095e7b73 100644 --- a/phpBB/includes/ucp/ucp_attachments.php +++ b/phpBB/includes/ucp/ucp_attachments.php @@ -171,8 +171,8 @@ class ucp_attachments $db->sql_freeresult($result); $base_url = $this->u_action . "&sk=$sort_key&sd=$sort_dir"; - phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $num_attachments, $config['topics_per_page'], $start); - + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $num_attachments, $config['topics_per_page'], $start); + $template->assign_vars(array( 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $num_attachments, $config['topics_per_page'], $start), 'TOTAL_ATTACHMENTS' => $num_attachments, diff --git a/phpBB/includes/ucp/ucp_pm_viewfolder.php b/phpBB/includes/ucp/ucp_pm_viewfolder.php index 1026f24699..625da23736 100644 --- a/phpBB/includes/ucp/ucp_pm_viewfolder.php +++ b/phpBB/includes/ucp/ucp_pm_viewfolder.php @@ -453,7 +453,7 @@ function get_pm_from($folder_id, $folder, $user_id) $base_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=view&action=view_folder&f=$folder_id&$u_sort_param"); phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $pm_count, $config['topics_per_page'], $start); - + $template->assign_vars(array( 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $pm_count, $config['topics_per_page'], $start), 'TOTAL_MESSAGES' => $user->lang('VIEW_PM_MESSAGES', (int) $pm_count), diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index f142d182bc..d9ba147c70 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -1593,7 +1593,7 @@ switch ($mode) } phpbb_generate_template_pagination($template, $pagination_url, 'pagination', 'start', $total_users, $config['topics_per_page'], $start); - + // Generate page $template->assign_vars(array( 'PAGE_NUMBER' => phpbb_on_page($template, $user, $pagination_url, $total_users, $config['topics_per_page'], $start), diff --git a/phpBB/search.php b/phpBB/search.php index 7eda3c4d1d..7d20d8d4a2 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -614,7 +614,7 @@ if ($keywords || $author || $author_id || $search_id || $submit) } phpbb_generate_template_pagination($template, $u_search, 'pagination', 'start', $total_match_count, $per_page, $start); - + $template->assign_vars(array( 'SEARCH_TITLE' => $l_search_title, 'SEARCH_MATCHES' => $l_search_matches, @@ -1013,7 +1013,7 @@ if ($keywords || $author || $author_id || $search_id || $submit) 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id), 'U_VIEW_POST' => (!empty($row['post_id'])) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=" . $row['topic_id'] . '&p=' . $row['post_id'] . (($u_hilit) ? '&hilit=' . $u_hilit : '')) . '#p' . $row['post_id'] : '') )); - + if ($show_results == 'topics') { phpbb_generate_template_pagination($template, $view_topic_url, 'searchresults.pagination', 'start', $replies + 1, $config['posts_per_page'], 1, true, true); From 7675d72622cf47789c83c9a243d17b0db37b72d2 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 17 Oct 2012 22:36:37 +0200 Subject: [PATCH 0808/1142] [ticket/11014] Fix text for previous/next links in Subsilver2 PHPBB3-11014 --- phpBB/styles/subsilver2/template/pagination.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/styles/subsilver2/template/pagination.html b/phpBB/styles/subsilver2/template/pagination.html index f78bb554fc..a2e023ac22 100644 --- a/phpBB/styles/subsilver2/template/pagination.html +++ b/phpBB/styles/subsilver2/template/pagination.html @@ -1,10 +1,10 @@ {L_GOTO_PAGE} - {pagination.PAGE_NUMBER} + {L_PREVIOUS} {pagination.PAGE_NUMBER} {L_ELLIPSIS} - {pagination.PAGE_NUMBER} + {L_NEXT} {pagination.PAGE_NUMBER} From 7ce43d49d8aadeab7db316741cc9d2503fee844c Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 17 Oct 2012 22:38:18 +0200 Subject: [PATCH 0809/1142] [ticket/11014] Fix IF statements for new template pagination PHPBB3-11014 --- phpBB/styles/subsilver2/template/mcp_footer.html | 2 +- phpBB/styles/subsilver2/template/ucp_groups_manage.html | 2 +- phpBB/styles/subsilver2/template/viewonline_body.html | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/styles/subsilver2/template/mcp_footer.html b/phpBB/styles/subsilver2/template/mcp_footer.html index b48c244653..280920b148 100644 --- a/phpBB/styles/subsilver2/template/mcp_footer.html +++ b/phpBB/styles/subsilver2/template/mcp_footer.html @@ -3,7 +3,7 @@ - + diff --git a/phpBB/styles/subsilver2/template/ucp_groups_manage.html b/phpBB/styles/subsilver2/template/ucp_groups_manage.html index ac678895a6..decd40a6de 100644 --- a/phpBB/styles/subsilver2/template/ucp_groups_manage.html +++ b/phpBB/styles/subsilver2/template/ucp_groups_manage.html @@ -185,7 +185,7 @@
-
diff --git a/phpBB/adm/style/captcha_default_acp_demo.html b/phpBB/adm/style/captcha_default_acp_demo.html index 0b1434f7e0..0f137f28df 100644 --- a/phpBB/adm/style/captcha_default_acp_demo.html +++ b/phpBB/adm/style/captcha_default_acp_demo.html @@ -1,4 +1,4 @@
-

{L_CAPTCHA_PREVIEW_EXPLAIN}
+

{L_CAPTCHA_PREVIEW_EXPLAIN}
{L_PREVIEW}
diff --git a/phpBB/adm/style/captcha_gd_acp.html b/phpBB/adm/style/captcha_gd_acp.html index e2804bbc7d..b333d9205c 100644 --- a/phpBB/adm/style/captcha_gd_acp.html +++ b/phpBB/adm/style/captcha_gd_acp.html @@ -14,32 +14,32 @@ {L_GENERAL_OPTIONS}
-

{L_CAPTCHA_GD_FOREGROUND_NOISE_EXPLAIN}
+

{L_CAPTCHA_GD_FOREGROUND_NOISE_EXPLAIN}
-

{L_CAPTCHA_GD_X_GRID_EXPLAIN}
+

{L_CAPTCHA_GD_X_GRID_EXPLAIN}
-

{L_CAPTCHA_GD_Y_GRID_EXPLAIN}
+

{L_CAPTCHA_GD_Y_GRID_EXPLAIN}
-

{L_CAPTCHA_GD_WAVE_EXPLAIN}
+

{L_CAPTCHA_GD_WAVE_EXPLAIN}
-

{L_CAPTCHA_GD_3D_NOISE_EXPLAIN}
+

{L_CAPTCHA_GD_3D_NOISE_EXPLAIN}
-

{L_CAPTCHA_GD_FONTS_EXPLAIN}
+

{L_CAPTCHA_GD_FONTS_EXPLAIN}
diff --git a/phpBB/adm/style/captcha_qa_acp.html b/phpBB/adm/style/captcha_qa_acp.html index 4eb46d2d3c..99f3f24f70 100644 --- a/phpBB/adm/style/captcha_qa_acp.html +++ b/phpBB/adm/style/captcha_qa_acp.html @@ -59,7 +59,7 @@
{L_EDIT_QUESTION}
-

{L_QUESTION_STRICT_EXPLAIN}
+

{L_QUESTION_STRICT_EXPLAIN}
diff --git a/phpBB/adm/style/captcha_qa_acp_demo.html b/phpBB/adm/style/captcha_qa_acp_demo.html index 0ea2d6a024..79170e27c6 100644 --- a/phpBB/adm/style/captcha_qa_acp_demo.html +++ b/phpBB/adm/style/captcha_qa_acp_demo.html @@ -1,5 +1,5 @@
-

{L_CONFIRM_QUESTION_EXPLAIN}
+

{L_CONFIRM_QUESTION_EXPLAIN}
diff --git a/phpBB/adm/style/captcha_recaptcha_acp.html b/phpBB/adm/style/captcha_recaptcha_acp.html index 34912c7955..67176ebd07 100644 --- a/phpBB/adm/style/captcha_recaptcha_acp.html +++ b/phpBB/adm/style/captcha_recaptcha_acp.html @@ -13,11 +13,11 @@ {L_GENERAL_OPTIONS}
-

{L_RECAPTCHA_PUBLIC_EXPLAIN}
+

{L_RECAPTCHA_PUBLIC_EXPLAIN}
-

{L_RECAPTCHA_PRIVATE_EXPLAIN}
+

{L_RECAPTCHA_PRIVATE_EXPLAIN}
diff --git a/phpBB/adm/style/custom_profile_fields.html b/phpBB/adm/style/custom_profile_fields.html index 351397d3c7..982356bfa7 100644 --- a/phpBB/adm/style/custom_profile_fields.html +++ b/phpBB/adm/style/custom_profile_fields.html @@ -26,7 +26,7 @@ - {L_DAY}: - {L_MONTH}: - {L_YEAR}: + {L_DAY}{L_COLON} + {L_MONTH}{L_COLON} + {L_YEAR}{L_COLON} diff --git a/phpBB/adm/style/install_convert.html b/phpBB/adm/style/install_convert.html index cf78f30b50..5fec8c4789 100644 --- a/phpBB/adm/style/install_convert.html +++ b/phpBB/adm/style/install_convert.html @@ -86,7 +86,7 @@
-

{checks.TITLE_EXPLAIN}
+

{checks.TITLE_EXPLAIN}
{checks.RESULT}
@@ -109,7 +109,7 @@
-

{options.TITLE_EXPLAIN}
+

{options.TITLE_EXPLAIN}
{options.CONTENT}
diff --git a/phpBB/adm/style/install_header.html b/phpBB/adm/style/install_header.html index e306d8f6bf..6826654ded 100644 --- a/phpBB/adm/style/install_header.html +++ b/phpBB/adm/style/install_header.html @@ -42,7 +42,7 @@ function dE(n, s, type)
- + {S_LANG_SELECT}
diff --git a/phpBB/adm/style/install_install.html b/phpBB/adm/style/install_install.html index 79006fba69..1a809a3588 100644 --- a/phpBB/adm/style/install_install.html +++ b/phpBB/adm/style/install_install.html @@ -20,7 +20,7 @@
-
{checks.TITLE}:
{checks.TITLE_EXPLAIN}
+
{checks.TITLE}{L_COLON}
{checks.TITLE_EXPLAIN}
{checks.RESULT}
@@ -43,7 +43,7 @@
-

{options.TITLE_EXPLAIN}
+

{options.TITLE_EXPLAIN}
{options.CONTENT}
diff --git a/phpBB/adm/style/install_update.html b/phpBB/adm/style/install_update.html index 818889c89b..b5fa46dbf6 100644 --- a/phpBB/adm/style/install_update.html +++ b/phpBB/adm/style/install_update.html @@ -221,13 +221,13 @@
{new.DIR_PART}
{new.FILE_PART}
-
{L_FILE_USED}: {new.CUSTOM_ORIGINAL} +
{L_FILE_USED}{L_COLON} {new.CUSTOM_ORIGINAL}
-
+
[{new.L_SHOW_DIFF}]{L_BINARY_FILE}
-
+
@@ -245,11 +245,11 @@
{not_modified.DIR_PART}
{not_modified.FILE_PART}
-
{L_FILE_USED}: {not_modified.CUSTOM_ORIGINAL} +
{L_FILE_USED}{L_COLON} {not_modified.CUSTOM_ORIGINAL}
-
[{not_modified.L_SHOW_DIFF}]{L_BINARY_FILE}
+
[{not_modified.L_SHOW_DIFF}]{L_BINARY_FILE}
-
+
@@ -266,24 +266,24 @@ {L_STATUS_MODIFIED}
{modified.DIR_PART}
{modified.FILE_PART}
-
{L_FILE_USED}: {modified.CUSTOM_ORIGINAL} +
{L_FILE_USED}{L_COLON} {modified.CUSTOM_ORIGINAL}
-
 
+
 
-
+
-
[{modified.L_SHOW_DIFF}]{L_BINARY_FILE}
+
[{modified.L_SHOW_DIFF}]{L_BINARY_FILE}
-
[{L_SHOW_DIFF_FINAL}] 
+
[{L_SHOW_DIFF_FINAL}] 
-
[{L_SHOW_DIFF_FINAL}] 
+
[{L_SHOW_DIFF_FINAL}] 
@@ -299,13 +299,13 @@
{new_conflict.DIR_PART}
{new_conflict.FILE_PART}
-
{L_FILE_USED}: {new_conflict.CUSTOM_ORIGINAL} +
{L_FILE_USED}{L_COLON} {new_conflict.CUSTOM_ORIGINAL}
-
+
[{new_conflict.L_SHOW_DIFF}]{L_BINARY_FILE}
-
+
@@ -322,38 +322,38 @@ {L_STATUS_CONFLICT}
{conflict.DIR_PART}
{conflict.FILE_PART}
-
{L_FILE_USED}: {conflict.CUSTOM_ORIGINAL} -
{L_NUM_CONFLICTS}: {conflict.NUM_CONFLICTS} +
{L_FILE_USED}{L_COLON} {conflict.CUSTOM_ORIGINAL} +
{L_NUM_CONFLICTS}{L_COLON} {conflict.NUM_CONFLICTS}
-
+
[{L_DOWNLOAD_CONFLICTS}]
{L_DOWNLOAD_CONFLICTS_EXPLAIN} {L_BINARY_FILE}
-
+
-
 
+
 
-
[{L_SHOW_DIFF_MODIFIED}]
+
[{L_SHOW_DIFF_MODIFIED}]
-
[{L_SHOW_DIFF_MODIFIED}]
+
[{L_SHOW_DIFF_MODIFIED}]
-
[{L_SHOW_DIFF_FINAL}]
+
[{L_SHOW_DIFF_FINAL}]
-
[{L_SHOW_DIFF_FINAL}]
+
[{L_SHOW_DIFF_FINAL}]
@@ -392,7 +392,7 @@
{L_SELECT_DOWNLOAD_FORMAT}
-
+
{RADIO_BUTTONS}
@@ -455,12 +455,12 @@
{L_FTP_SETTINGS}
-
+
{UPLOAD_METHOD}
-

{data.EXPLAIN}
+

{data.EXPLAIN}
diff --git a/phpBB/adm/style/install_update_diff.html b/phpBB/adm/style/install_update_diff.html index b5d25e82f2..ecdc40e157 100644 --- a/phpBB/adm/style/install_update_diff.html +++ b/phpBB/adm/style/install_update_diff.html @@ -223,7 +223,7 @@ table.hrdiff caption span {

{L_SKIP}

- + @@ -231,7 +231,7 @@ table.hrdiff caption span { -
{L_NUM_CONFLICTS}: {NUM_CONFLICTS}
+
{L_NUM_CONFLICTS}{L_COLON} {NUM_CONFLICTS}

diff --git a/phpBB/adm/style/overall_header.html b/phpBB/adm/style/overall_header.html index f6d0e1025f..f7902e6d2e 100644 --- a/phpBB/adm/style/overall_header.html +++ b/phpBB/adm/style/overall_header.html @@ -9,7 +9,7 @@ '; - $this->context->append_var('SCRIPTS', $code); - } -} + $user->img('icon_contact', 'CONTACT', 'full'); +* +* More in-depth... +* yadayada +*/ + +/** +* Base Template class. +* @package phpBB3 +*/ +class phpbb_template +{ + /** + * Template context. + * Stores template data used during template rendering. + * @var phpbb_template_context + */ + private $context; + + /** + * Path of the cache directory for the template + * @var string + */ + public $cachepath = ''; + + /** + * phpBB root path + * @var string + */ + private $phpbb_root_path; + + /** + * PHP file extension + * @var string + */ + private $php_ext; + + /** + * phpBB config instance + * @var phpbb_config + */ + private $config; + + /** + * Current user + * @var phpbb_user + */ + private $user; + + /** + * Template locator + * @var phpbb_template_locator + */ + private $locator; + + /** + * Location of templates directory within style directories + * @var string + */ + public $template_path = 'template/'; + + /** + * Constructor. + * + * @param string $phpbb_root_path phpBB root path + * @param user $user current user + * @param phpbb_template_locator $locator template locator + * @param phpbb_template_context $context template context + */ + public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->user = $user; + $this->locator = $locator; + $this->template_path = $this->locator->template_path; + $this->context = $context; + } + + /** + * Sets the template filenames for handles. + * + * @param array $filname_array Should be a hash of handle => filename pairs. + */ + public function set_filenames(array $filename_array) + { + $this->locator->set_filenames($filename_array); + + return true; + } + + /** + * Clears all variables and blocks assigned to this template. + */ + public function destroy() + { + $this->context->clear(); + } + + /** + * Reset/empty complete block + * + * @param string $blockname Name of block to destroy + */ + public function destroy_block_vars($blockname) + { + $this->context->destroy_block_vars($blockname); + } + + /** + * Display a template for provided handle. + * + * The template will be loaded and compiled, if necessary, first. + * + * This function calls hooks. + * + * @param string $handle Handle to display + * @return bool True on success, false on failure + */ + public function display($handle) + { + $result = $this->call_hook($handle, __FUNCTION__); + if ($result !== false) + { + return $result[0]; + } + + return $this->load_and_render($handle); + } + + /** + * Loads a template for $handle, compiling it if necessary, and + * renders the template. + * + * @param string $handle Template handle to render + * @return bool True on success, false on failure + */ + private function load_and_render($handle) + { + $renderer = $this->_tpl_load($handle); + + if ($renderer) + { + $renderer->render($this->context, $this->get_lang()); + return true; + } + else + { + return false; + } + } + + /** + * Calls hook if any is defined. + * + * @param string $handle Template handle being displayed. + * @param string $method Method name of the caller. + */ + private function call_hook($handle, $method) + { + global $phpbb_hook; + + if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, $method), $handle, $this)) + { + if ($phpbb_hook->hook_return(array(__CLASS__, $method))) + { + $result = $phpbb_hook->hook_return_result(array(__CLASS__, $method)); + return array($result); + } + } + + return false; + } + + /** + * Obtains language array. + * This is either lang property of $user property, or if + * it is not set an empty array. + * @return array language entries + */ + public function get_lang() + { + if (isset($this->user->lang)) + { + $lang = $this->user->lang; + } + else + { + $lang = array(); + } + return $lang; + } + + /** + * Display the handle and assign the output to a template variable + * or return the compiled result. + * + * @param string $handle Handle to operate on + * @param string $template_var Template variable to assign compiled handle to + * @param bool $return_content If true return compiled handle, otherwise assign to $template_var + * @return bool|string false on failure, otherwise if $return_content is true return string of the compiled handle, otherwise return true + */ + public function assign_display($handle, $template_var = '', $return_content = true) + { + ob_start(); + $result = $this->display($handle); + $contents = ob_get_clean(); + if ($result === false) + { + return false; + } + + if ($return_content) + { + return $contents; + } + + $this->assign_var($template_var, $contents); + + return true; + } + + /** + * Obtains a template renderer for a template identified by specified + * handle. The template renderer can display the template later. + * + * Template source will first be compiled into php code. + * If template cache is writable the compiled php code will be stored + * on filesystem and template will not be subsequently recompiled. + * If template cache is not writable template source will be recompiled + * every time it is needed. DEBUG define and load_tplcompile + * configuration setting may be used to force templates to be always + * recompiled. + * + * Returns an object implementing phpbb_template_renderer, or null + * if template loading or compilation failed. Call render() on the + * renderer to display the template. This will result in template + * contents sent to the output stream (unless, of course, output + * buffering is in effect). + * + * @param string $handle Handle of the template to load + * @return phpbb_template_renderer Template renderer object, or null on failure + * @uses phpbb_template_compile is used to compile template source + */ + private function _tpl_load($handle) + { + $output_file = $this->_compiled_file_for_handle($handle); + + $recompile = defined('DEBUG') || + !file_exists($output_file) || + @filesize($output_file) === 0; + + if ($recompile || $this->config['load_tplcompile']) + { + // Set only if a recompile or an mtime check are required. + $source_file = $this->locator->get_source_file_for_handle($handle); + + if (!$recompile && @filemtime($output_file) < @filemtime($source_file)) + { + $recompile = true; + } + } + + // Recompile page if the original template is newer, otherwise load the compiled version + if (!$recompile) + { + return new phpbb_template_renderer_include($output_file, $this); + } + + $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->locator, $this->phpbb_root_path); + + if ($compile->compile_file_to_file($source_file, $output_file) !== false) + { + $renderer = new phpbb_template_renderer_include($output_file, $this); + } + else if (($code = $compile->compile_file($source_file)) !== false) + { + $renderer = new phpbb_template_renderer_eval($code, $this); + } + else + { + $renderer = null; + } + + return $renderer; + } + + /** + * Determines compiled file path for handle $handle. + * + * @param string $handle Template handle (i.e. "friendly" template name) + * @return string Compiled file path + */ + private function _compiled_file_for_handle($handle) + { + $source_file = $this->locator->get_filename_for_handle($handle); + $compiled_file = $this->cachepath . str_replace('/', '.', $source_file) . '.' . $this->php_ext; + return $compiled_file; + } + + /** + * Assign key variable pairs from an array + * + * @param array $vararray A hash of variable name => value pairs + */ + public function assign_vars(array $vararray) + { + foreach ($vararray as $key => $val) + { + $this->assign_var($key, $val); + } + } + + /** + * Assign a single scalar value to a single key. + * + * Value can be a string, an integer or a boolean. + * + * @param string $varname Variable name + * @param string $varval Value to assign to variable + */ + public function assign_var($varname, $varval) + { + $this->context->assign_var($varname, $varval); + } + + /** + * Append text to the string value stored in a key. + * + * Text is appended using the string concatenation operator (.). + * + * @param string $varname Variable name + * @param string $varval Value to append to variable + */ + public function append_var($varname, $varval) + { + $this->context->append_var($varname, $varval); + } + + // Docstring is copied from phpbb_template_context method with the same name. + /** + * Assign key variable pairs from an array to a specified block + * @param string $blockname Name of block to assign $vararray to + * @param array $vararray A hash of variable name => value pairs + */ + public function assign_block_vars($blockname, array $vararray) + { + return $this->context->assign_block_vars($blockname, $vararray); + } + + // Docstring is copied from phpbb_template_context method with the same name. + /** + * Change already assigned key variable pair (one-dimensional - single loop entry) + * + * An example of how to use this function: + * {@example alter_block_array.php} + * + * @param string $blockname the blockname, for example 'loop' + * @param array $vararray the var array to insert/add or merge + * @param mixed $key Key to search for + * + * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] + * + * int: Position [the position to change or insert at directly given] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @param string $mode Mode to execute (valid modes are 'insert' and 'change') + * + * If insert, the vararray is inserted at the given position (position counting from zero). + * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). + * + * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) + * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) + * + * @return bool false on error, true on success + */ + public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') + { + return $this->context->alter_block_array($blockname, $vararray, $key, $mode); + } + + /** + * Include a separate template. + * + * This function is marked public due to the way the template + * implementation uses it. It is actually an implementation function + * and should not be considered part of template class's public API. + * + * @param string $filename Template filename to include + * @param bool $include True to include the file, false to just load it + * @uses template_compile is used to compile uncached templates + */ + public function _tpl_include($filename, $include = true) + { + $this->locator->set_filenames(array($filename => $filename)); + + if (!$this->load_and_render($filename)) + { + // trigger_error cannot be used here, as the output already started + echo 'template->_tpl_include(): Failed including ' . htmlspecialchars($handle) . "\n"; + } + } + + /** + * Include a PHP file. + * + * If a relative path is passed in $filename, it is considered to be + * relative to board root ($phpbb_root_path). Absolute paths are + * also allowed. + * + * This function is marked public due to the way the template + * implementation uses it. It is actually an implementation function + * and should not be considered part of template class's public API. + * + * @param string $filename Path to PHP file to include + */ + public function _php_include($filename) + { + if (phpbb_is_absolute($filename)) + { + $file = $filename; + } + else + { + $file = $this->phpbb_root_path . $filename; + } + + if (!file_exists($file)) + { + // trigger_error cannot be used here, as the output already started + echo 'template->_php_include(): File ' . htmlspecialchars($file) . " does not exist\n"; + return; + } + include($file); + } + + /** + * Obtains filesystem path for a template file. + * + * The simplest use is specifying a single template file as a string + * in the first argument. This template file should be a basename + * of a template file in the selected style, or its parent styles + * if template inheritance is being utilized. + * + * Note: "selected style" is whatever style the style resource locator + * is configured for. + * + * The return value then will be a path, relative to the current + * directory or absolute, to the template file in the selected style + * or its closest parent. + * + * If the selected style does not have the template file being searched, + * (and if inheritance is involved, none of the parents have it either), + * false will be returned. + * + * Specifying true for $return_default will cause the function to + * return the first path which was checked for existence in the event + * that the template file was not found, instead of false. + * This is the path in the selected style itself, not any of its + * parents. + * + * $files can be given an array of templates instead of a single + * template. When given an array, the function will try to resolve + * each template in the array to a path, and will return the first + * path that exists, or false if none exist. + * + * If $return_full_path is false, then instead of returning a usable + * path (when the template is found) only the template's basename + * will be returned. This can be used to check which of the templates + * specified in $files exists, provided different file names are + * used for different templates. + * + * @param string or array $files List of templates to locate. If there is only + * one template, $files can be a string to make code easier to read. + * @param bool $return_default Determines what to return if template does not + * exist. If true, function will return location where template is + * supposed to be. If false, function will return false. + * @param bool $return_full_path If true, function will return full path + * to template. If false, function will return template file name. + * This parameter can be used to check which one of set of template + * files is available. + * @return string or boolean Source template path if template exists or $return_default is + * true. False if template does not exist and $return_default is false + */ + public function locate($files, $return_default = false, $return_full_path = true) + { + // add template path prefix + $templates = array(); + if (is_string($files)) + { + $templates[] = $this->template_path . $files; + } + else + { + foreach ($files as $file) + { + $templates[] = $this->template_path . $file; + } + } + + // use resource locator to find files + return $this->locator->get_first_file_location($templates, $return_default, $return_full_path); + } + + /** + * Include JS file + * + * @param string $file file name + * @param bool $locate True if file needs to be located + * @param bool $relative True if path is relative to phpBB root directory. Ignored if $locate == true + */ + public function _js_include($file, $locate = false, $relative = false) + { + // Locate file + if ($locate) + { + $located = $this->locator->get_first_file_location(array($file), false, true); + if ($located) + { + $file = $located; + } + } + else if ($relative) + { + $file = $this->phpbb_root_path . $file; + } + + $file .= (strpos($file, '?') === false) ? '?' : '&'; + $file .= 'assets_version=' . $this->config['assets_version']; + + // Add HTML code + $code = ''; + $this->context->append_var('SCRIPTS', $code); + } +} From 2e594504596eaf6ab758240a1cd012dfea79fe77 Mon Sep 17 00:00:00 2001 From: David King Date: Tue, 13 Nov 2012 10:42:20 -0500 Subject: [PATCH 0985/1142] [feature/controller] Use assign_display() instead of return_display() The latter was deemed unnecessary at the moment. PHPBB3-10864 --- phpBB/includes/controller/helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/controller/helper.php b/phpBB/includes/controller/helper.php index 0fc3ba0c67..8bb4427382 100644 --- a/phpBB/includes/controller/helper.php +++ b/phpBB/includes/controller/helper.php @@ -87,7 +87,7 @@ class phpbb_controller_helper page_footer(true, false, false); - return new Response($this->template->return_display('body'), $status_code); + return new Response($this->template->assign_display('body'), $status_code); } /** From c6a5699325171ca12c540772ed4e6c0c55318ec5 Mon Sep 17 00:00:00 2001 From: David King Date: Tue, 13 Nov 2012 10:46:46 -0500 Subject: [PATCH 0986/1142] [feature/controller-new] Fix line endings PHPBB3-10864 --- phpBB/includes/template/template.php | 1118 +++++++++++++------------- 1 file changed, 559 insertions(+), 559 deletions(-) diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index 00fe26b9b1..8a7dc6b2f3 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -1,559 +1,559 @@ - $user->img('icon_contact', 'CONTACT', 'full'); -* -* More in-depth... -* yadayada -*/ - -/** -* Base Template class. -* @package phpBB3 -*/ -class phpbb_template -{ - /** - * Template context. - * Stores template data used during template rendering. - * @var phpbb_template_context - */ - private $context; - - /** - * Path of the cache directory for the template - * @var string - */ - public $cachepath = ''; - - /** - * phpBB root path - * @var string - */ - private $phpbb_root_path; - - /** - * PHP file extension - * @var string - */ - private $php_ext; - - /** - * phpBB config instance - * @var phpbb_config - */ - private $config; - - /** - * Current user - * @var phpbb_user - */ - private $user; - - /** - * Template locator - * @var phpbb_template_locator - */ - private $locator; - - /** - * Location of templates directory within style directories - * @var string - */ - public $template_path = 'template/'; - - /** - * Constructor. - * - * @param string $phpbb_root_path phpBB root path - * @param user $user current user - * @param phpbb_template_locator $locator template locator - * @param phpbb_template_context $context template context - */ - public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context) - { - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->config = $config; - $this->user = $user; - $this->locator = $locator; - $this->template_path = $this->locator->template_path; - $this->context = $context; - } - - /** - * Sets the template filenames for handles. - * - * @param array $filname_array Should be a hash of handle => filename pairs. - */ - public function set_filenames(array $filename_array) - { - $this->locator->set_filenames($filename_array); - - return true; - } - - /** - * Clears all variables and blocks assigned to this template. - */ - public function destroy() - { - $this->context->clear(); - } - - /** - * Reset/empty complete block - * - * @param string $blockname Name of block to destroy - */ - public function destroy_block_vars($blockname) - { - $this->context->destroy_block_vars($blockname); - } - - /** - * Display a template for provided handle. - * - * The template will be loaded and compiled, if necessary, first. - * - * This function calls hooks. - * - * @param string $handle Handle to display - * @return bool True on success, false on failure - */ - public function display($handle) - { - $result = $this->call_hook($handle, __FUNCTION__); - if ($result !== false) - { - return $result[0]; - } - - return $this->load_and_render($handle); - } - - /** - * Loads a template for $handle, compiling it if necessary, and - * renders the template. - * - * @param string $handle Template handle to render - * @return bool True on success, false on failure - */ - private function load_and_render($handle) - { - $renderer = $this->_tpl_load($handle); - - if ($renderer) - { - $renderer->render($this->context, $this->get_lang()); - return true; - } - else - { - return false; - } - } - - /** - * Calls hook if any is defined. - * - * @param string $handle Template handle being displayed. - * @param string $method Method name of the caller. - */ - private function call_hook($handle, $method) - { - global $phpbb_hook; - - if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, $method), $handle, $this)) - { - if ($phpbb_hook->hook_return(array(__CLASS__, $method))) - { - $result = $phpbb_hook->hook_return_result(array(__CLASS__, $method)); - return array($result); - } - } - - return false; - } - - /** - * Obtains language array. - * This is either lang property of $user property, or if - * it is not set an empty array. - * @return array language entries - */ - public function get_lang() - { - if (isset($this->user->lang)) - { - $lang = $this->user->lang; - } - else - { - $lang = array(); - } - return $lang; - } - - /** - * Display the handle and assign the output to a template variable - * or return the compiled result. - * - * @param string $handle Handle to operate on - * @param string $template_var Template variable to assign compiled handle to - * @param bool $return_content If true return compiled handle, otherwise assign to $template_var - * @return bool|string false on failure, otherwise if $return_content is true return string of the compiled handle, otherwise return true - */ - public function assign_display($handle, $template_var = '', $return_content = true) - { - ob_start(); - $result = $this->display($handle); - $contents = ob_get_clean(); - if ($result === false) - { - return false; - } - - if ($return_content) - { - return $contents; - } - - $this->assign_var($template_var, $contents); - - return true; - } - - /** - * Obtains a template renderer for a template identified by specified - * handle. The template renderer can display the template later. - * - * Template source will first be compiled into php code. - * If template cache is writable the compiled php code will be stored - * on filesystem and template will not be subsequently recompiled. - * If template cache is not writable template source will be recompiled - * every time it is needed. DEBUG define and load_tplcompile - * configuration setting may be used to force templates to be always - * recompiled. - * - * Returns an object implementing phpbb_template_renderer, or null - * if template loading or compilation failed. Call render() on the - * renderer to display the template. This will result in template - * contents sent to the output stream (unless, of course, output - * buffering is in effect). - * - * @param string $handle Handle of the template to load - * @return phpbb_template_renderer Template renderer object, or null on failure - * @uses phpbb_template_compile is used to compile template source - */ - private function _tpl_load($handle) - { - $output_file = $this->_compiled_file_for_handle($handle); - - $recompile = defined('DEBUG') || - !file_exists($output_file) || - @filesize($output_file) === 0; - - if ($recompile || $this->config['load_tplcompile']) - { - // Set only if a recompile or an mtime check are required. - $source_file = $this->locator->get_source_file_for_handle($handle); - - if (!$recompile && @filemtime($output_file) < @filemtime($source_file)) - { - $recompile = true; - } - } - - // Recompile page if the original template is newer, otherwise load the compiled version - if (!$recompile) - { - return new phpbb_template_renderer_include($output_file, $this); - } - - $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->locator, $this->phpbb_root_path); - - if ($compile->compile_file_to_file($source_file, $output_file) !== false) - { - $renderer = new phpbb_template_renderer_include($output_file, $this); - } - else if (($code = $compile->compile_file($source_file)) !== false) - { - $renderer = new phpbb_template_renderer_eval($code, $this); - } - else - { - $renderer = null; - } - - return $renderer; - } - - /** - * Determines compiled file path for handle $handle. - * - * @param string $handle Template handle (i.e. "friendly" template name) - * @return string Compiled file path - */ - private function _compiled_file_for_handle($handle) - { - $source_file = $this->locator->get_filename_for_handle($handle); - $compiled_file = $this->cachepath . str_replace('/', '.', $source_file) . '.' . $this->php_ext; - return $compiled_file; - } - - /** - * Assign key variable pairs from an array - * - * @param array $vararray A hash of variable name => value pairs - */ - public function assign_vars(array $vararray) - { - foreach ($vararray as $key => $val) - { - $this->assign_var($key, $val); - } - } - - /** - * Assign a single scalar value to a single key. - * - * Value can be a string, an integer or a boolean. - * - * @param string $varname Variable name - * @param string $varval Value to assign to variable - */ - public function assign_var($varname, $varval) - { - $this->context->assign_var($varname, $varval); - } - - /** - * Append text to the string value stored in a key. - * - * Text is appended using the string concatenation operator (.). - * - * @param string $varname Variable name - * @param string $varval Value to append to variable - */ - public function append_var($varname, $varval) - { - $this->context->append_var($varname, $varval); - } - - // Docstring is copied from phpbb_template_context method with the same name. - /** - * Assign key variable pairs from an array to a specified block - * @param string $blockname Name of block to assign $vararray to - * @param array $vararray A hash of variable name => value pairs - */ - public function assign_block_vars($blockname, array $vararray) - { - return $this->context->assign_block_vars($blockname, $vararray); - } - - // Docstring is copied from phpbb_template_context method with the same name. - /** - * Change already assigned key variable pair (one-dimensional - single loop entry) - * - * An example of how to use this function: - * {@example alter_block_array.php} - * - * @param string $blockname the blockname, for example 'loop' - * @param array $vararray the var array to insert/add or merge - * @param mixed $key Key to search for - * - * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] - * - * int: Position [the position to change or insert at directly given] - * - * If key is false the position is set to 0 - * If key is true the position is set to the last entry - * - * @param string $mode Mode to execute (valid modes are 'insert' and 'change') - * - * If insert, the vararray is inserted at the given position (position counting from zero). - * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). - * - * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) - * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) - * - * @return bool false on error, true on success - */ - public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') - { - return $this->context->alter_block_array($blockname, $vararray, $key, $mode); - } - - /** - * Include a separate template. - * - * This function is marked public due to the way the template - * implementation uses it. It is actually an implementation function - * and should not be considered part of template class's public API. - * - * @param string $filename Template filename to include - * @param bool $include True to include the file, false to just load it - * @uses template_compile is used to compile uncached templates - */ - public function _tpl_include($filename, $include = true) - { - $this->locator->set_filenames(array($filename => $filename)); - - if (!$this->load_and_render($filename)) - { - // trigger_error cannot be used here, as the output already started - echo 'template->_tpl_include(): Failed including ' . htmlspecialchars($handle) . "\n"; - } - } - - /** - * Include a PHP file. - * - * If a relative path is passed in $filename, it is considered to be - * relative to board root ($phpbb_root_path). Absolute paths are - * also allowed. - * - * This function is marked public due to the way the template - * implementation uses it. It is actually an implementation function - * and should not be considered part of template class's public API. - * - * @param string $filename Path to PHP file to include - */ - public function _php_include($filename) - { - if (phpbb_is_absolute($filename)) - { - $file = $filename; - } - else - { - $file = $this->phpbb_root_path . $filename; - } - - if (!file_exists($file)) - { - // trigger_error cannot be used here, as the output already started - echo 'template->_php_include(): File ' . htmlspecialchars($file) . " does not exist\n"; - return; - } - include($file); - } - - /** - * Obtains filesystem path for a template file. - * - * The simplest use is specifying a single template file as a string - * in the first argument. This template file should be a basename - * of a template file in the selected style, or its parent styles - * if template inheritance is being utilized. - * - * Note: "selected style" is whatever style the style resource locator - * is configured for. - * - * The return value then will be a path, relative to the current - * directory or absolute, to the template file in the selected style - * or its closest parent. - * - * If the selected style does not have the template file being searched, - * (and if inheritance is involved, none of the parents have it either), - * false will be returned. - * - * Specifying true for $return_default will cause the function to - * return the first path which was checked for existence in the event - * that the template file was not found, instead of false. - * This is the path in the selected style itself, not any of its - * parents. - * - * $files can be given an array of templates instead of a single - * template. When given an array, the function will try to resolve - * each template in the array to a path, and will return the first - * path that exists, or false if none exist. - * - * If $return_full_path is false, then instead of returning a usable - * path (when the template is found) only the template's basename - * will be returned. This can be used to check which of the templates - * specified in $files exists, provided different file names are - * used for different templates. - * - * @param string or array $files List of templates to locate. If there is only - * one template, $files can be a string to make code easier to read. - * @param bool $return_default Determines what to return if template does not - * exist. If true, function will return location where template is - * supposed to be. If false, function will return false. - * @param bool $return_full_path If true, function will return full path - * to template. If false, function will return template file name. - * This parameter can be used to check which one of set of template - * files is available. - * @return string or boolean Source template path if template exists or $return_default is - * true. False if template does not exist and $return_default is false - */ - public function locate($files, $return_default = false, $return_full_path = true) - { - // add template path prefix - $templates = array(); - if (is_string($files)) - { - $templates[] = $this->template_path . $files; - } - else - { - foreach ($files as $file) - { - $templates[] = $this->template_path . $file; - } - } - - // use resource locator to find files - return $this->locator->get_first_file_location($templates, $return_default, $return_full_path); - } - - /** - * Include JS file - * - * @param string $file file name - * @param bool $locate True if file needs to be located - * @param bool $relative True if path is relative to phpBB root directory. Ignored if $locate == true - */ - public function _js_include($file, $locate = false, $relative = false) - { - // Locate file - if ($locate) - { - $located = $this->locator->get_first_file_location(array($file), false, true); - if ($located) - { - $file = $located; - } - } - else if ($relative) - { - $file = $this->phpbb_root_path . $file; - } - - $file .= (strpos($file, '?') === false) ? '?' : '&'; - $file .= 'assets_version=' . $this->config['assets_version']; - - // Add HTML code - $code = ''; - $this->context->append_var('SCRIPTS', $code); - } -} + $user->img('icon_contact', 'CONTACT', 'full'); +* +* More in-depth... +* yadayada +*/ + +/** +* Base Template class. +* @package phpBB3 +*/ +class phpbb_template +{ + /** + * Template context. + * Stores template data used during template rendering. + * @var phpbb_template_context + */ + private $context; + + /** + * Path of the cache directory for the template + * @var string + */ + public $cachepath = ''; + + /** + * phpBB root path + * @var string + */ + private $phpbb_root_path; + + /** + * PHP file extension + * @var string + */ + private $php_ext; + + /** + * phpBB config instance + * @var phpbb_config + */ + private $config; + + /** + * Current user + * @var phpbb_user + */ + private $user; + + /** + * Template locator + * @var phpbb_template_locator + */ + private $locator; + + /** + * Location of templates directory within style directories + * @var string + */ + public $template_path = 'template/'; + + /** + * Constructor. + * + * @param string $phpbb_root_path phpBB root path + * @param user $user current user + * @param phpbb_template_locator $locator template locator + * @param phpbb_template_context $context template context + */ + public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->user = $user; + $this->locator = $locator; + $this->template_path = $this->locator->template_path; + $this->context = $context; + } + + /** + * Sets the template filenames for handles. + * + * @param array $filname_array Should be a hash of handle => filename pairs. + */ + public function set_filenames(array $filename_array) + { + $this->locator->set_filenames($filename_array); + + return true; + } + + /** + * Clears all variables and blocks assigned to this template. + */ + public function destroy() + { + $this->context->clear(); + } + + /** + * Reset/empty complete block + * + * @param string $blockname Name of block to destroy + */ + public function destroy_block_vars($blockname) + { + $this->context->destroy_block_vars($blockname); + } + + /** + * Display a template for provided handle. + * + * The template will be loaded and compiled, if necessary, first. + * + * This function calls hooks. + * + * @param string $handle Handle to display + * @return bool True on success, false on failure + */ + public function display($handle) + { + $result = $this->call_hook($handle, __FUNCTION__); + if ($result !== false) + { + return $result[0]; + } + + return $this->load_and_render($handle); + } + + /** + * Loads a template for $handle, compiling it if necessary, and + * renders the template. + * + * @param string $handle Template handle to render + * @return bool True on success, false on failure + */ + private function load_and_render($handle) + { + $renderer = $this->_tpl_load($handle); + + if ($renderer) + { + $renderer->render($this->context, $this->get_lang()); + return true; + } + else + { + return false; + } + } + + /** + * Calls hook if any is defined. + * + * @param string $handle Template handle being displayed. + * @param string $method Method name of the caller. + */ + private function call_hook($handle, $method) + { + global $phpbb_hook; + + if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, $method), $handle, $this)) + { + if ($phpbb_hook->hook_return(array(__CLASS__, $method))) + { + $result = $phpbb_hook->hook_return_result(array(__CLASS__, $method)); + return array($result); + } + } + + return false; + } + + /** + * Obtains language array. + * This is either lang property of $user property, or if + * it is not set an empty array. + * @return array language entries + */ + public function get_lang() + { + if (isset($this->user->lang)) + { + $lang = $this->user->lang; + } + else + { + $lang = array(); + } + return $lang; + } + + /** + * Display the handle and assign the output to a template variable + * or return the compiled result. + * + * @param string $handle Handle to operate on + * @param string $template_var Template variable to assign compiled handle to + * @param bool $return_content If true return compiled handle, otherwise assign to $template_var + * @return bool|string false on failure, otherwise if $return_content is true return string of the compiled handle, otherwise return true + */ + public function assign_display($handle, $template_var = '', $return_content = true) + { + ob_start(); + $result = $this->display($handle); + $contents = ob_get_clean(); + if ($result === false) + { + return false; + } + + if ($return_content) + { + return $contents; + } + + $this->assign_var($template_var, $contents); + + return true; + } + + /** + * Obtains a template renderer for a template identified by specified + * handle. The template renderer can display the template later. + * + * Template source will first be compiled into php code. + * If template cache is writable the compiled php code will be stored + * on filesystem and template will not be subsequently recompiled. + * If template cache is not writable template source will be recompiled + * every time it is needed. DEBUG define and load_tplcompile + * configuration setting may be used to force templates to be always + * recompiled. + * + * Returns an object implementing phpbb_template_renderer, or null + * if template loading or compilation failed. Call render() on the + * renderer to display the template. This will result in template + * contents sent to the output stream (unless, of course, output + * buffering is in effect). + * + * @param string $handle Handle of the template to load + * @return phpbb_template_renderer Template renderer object, or null on failure + * @uses phpbb_template_compile is used to compile template source + */ + private function _tpl_load($handle) + { + $output_file = $this->_compiled_file_for_handle($handle); + + $recompile = defined('DEBUG') || + !file_exists($output_file) || + @filesize($output_file) === 0; + + if ($recompile || $this->config['load_tplcompile']) + { + // Set only if a recompile or an mtime check are required. + $source_file = $this->locator->get_source_file_for_handle($handle); + + if (!$recompile && @filemtime($output_file) < @filemtime($source_file)) + { + $recompile = true; + } + } + + // Recompile page if the original template is newer, otherwise load the compiled version + if (!$recompile) + { + return new phpbb_template_renderer_include($output_file, $this); + } + + $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->locator, $this->phpbb_root_path); + + if ($compile->compile_file_to_file($source_file, $output_file) !== false) + { + $renderer = new phpbb_template_renderer_include($output_file, $this); + } + else if (($code = $compile->compile_file($source_file)) !== false) + { + $renderer = new phpbb_template_renderer_eval($code, $this); + } + else + { + $renderer = null; + } + + return $renderer; + } + + /** + * Determines compiled file path for handle $handle. + * + * @param string $handle Template handle (i.e. "friendly" template name) + * @return string Compiled file path + */ + private function _compiled_file_for_handle($handle) + { + $source_file = $this->locator->get_filename_for_handle($handle); + $compiled_file = $this->cachepath . str_replace('/', '.', $source_file) . '.' . $this->php_ext; + return $compiled_file; + } + + /** + * Assign key variable pairs from an array + * + * @param array $vararray A hash of variable name => value pairs + */ + public function assign_vars(array $vararray) + { + foreach ($vararray as $key => $val) + { + $this->assign_var($key, $val); + } + } + + /** + * Assign a single scalar value to a single key. + * + * Value can be a string, an integer or a boolean. + * + * @param string $varname Variable name + * @param string $varval Value to assign to variable + */ + public function assign_var($varname, $varval) + { + $this->context->assign_var($varname, $varval); + } + + /** + * Append text to the string value stored in a key. + * + * Text is appended using the string concatenation operator (.). + * + * @param string $varname Variable name + * @param string $varval Value to append to variable + */ + public function append_var($varname, $varval) + { + $this->context->append_var($varname, $varval); + } + + // Docstring is copied from phpbb_template_context method with the same name. + /** + * Assign key variable pairs from an array to a specified block + * @param string $blockname Name of block to assign $vararray to + * @param array $vararray A hash of variable name => value pairs + */ + public function assign_block_vars($blockname, array $vararray) + { + return $this->context->assign_block_vars($blockname, $vararray); + } + + // Docstring is copied from phpbb_template_context method with the same name. + /** + * Change already assigned key variable pair (one-dimensional - single loop entry) + * + * An example of how to use this function: + * {@example alter_block_array.php} + * + * @param string $blockname the blockname, for example 'loop' + * @param array $vararray the var array to insert/add or merge + * @param mixed $key Key to search for + * + * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] + * + * int: Position [the position to change or insert at directly given] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @param string $mode Mode to execute (valid modes are 'insert' and 'change') + * + * If insert, the vararray is inserted at the given position (position counting from zero). + * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). + * + * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) + * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) + * + * @return bool false on error, true on success + */ + public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') + { + return $this->context->alter_block_array($blockname, $vararray, $key, $mode); + } + + /** + * Include a separate template. + * + * This function is marked public due to the way the template + * implementation uses it. It is actually an implementation function + * and should not be considered part of template class's public API. + * + * @param string $filename Template filename to include + * @param bool $include True to include the file, false to just load it + * @uses template_compile is used to compile uncached templates + */ + public function _tpl_include($filename, $include = true) + { + $this->locator->set_filenames(array($filename => $filename)); + + if (!$this->load_and_render($filename)) + { + // trigger_error cannot be used here, as the output already started + echo 'template->_tpl_include(): Failed including ' . htmlspecialchars($handle) . "\n"; + } + } + + /** + * Include a PHP file. + * + * If a relative path is passed in $filename, it is considered to be + * relative to board root ($phpbb_root_path). Absolute paths are + * also allowed. + * + * This function is marked public due to the way the template + * implementation uses it. It is actually an implementation function + * and should not be considered part of template class's public API. + * + * @param string $filename Path to PHP file to include + */ + public function _php_include($filename) + { + if (phpbb_is_absolute($filename)) + { + $file = $filename; + } + else + { + $file = $this->phpbb_root_path . $filename; + } + + if (!file_exists($file)) + { + // trigger_error cannot be used here, as the output already started + echo 'template->_php_include(): File ' . htmlspecialchars($file) . " does not exist\n"; + return; + } + include($file); + } + + /** + * Obtains filesystem path for a template file. + * + * The simplest use is specifying a single template file as a string + * in the first argument. This template file should be a basename + * of a template file in the selected style, or its parent styles + * if template inheritance is being utilized. + * + * Note: "selected style" is whatever style the style resource locator + * is configured for. + * + * The return value then will be a path, relative to the current + * directory or absolute, to the template file in the selected style + * or its closest parent. + * + * If the selected style does not have the template file being searched, + * (and if inheritance is involved, none of the parents have it either), + * false will be returned. + * + * Specifying true for $return_default will cause the function to + * return the first path which was checked for existence in the event + * that the template file was not found, instead of false. + * This is the path in the selected style itself, not any of its + * parents. + * + * $files can be given an array of templates instead of a single + * template. When given an array, the function will try to resolve + * each template in the array to a path, and will return the first + * path that exists, or false if none exist. + * + * If $return_full_path is false, then instead of returning a usable + * path (when the template is found) only the template's basename + * will be returned. This can be used to check which of the templates + * specified in $files exists, provided different file names are + * used for different templates. + * + * @param string or array $files List of templates to locate. If there is only + * one template, $files can be a string to make code easier to read. + * @param bool $return_default Determines what to return if template does not + * exist. If true, function will return location where template is + * supposed to be. If false, function will return false. + * @param bool $return_full_path If true, function will return full path + * to template. If false, function will return template file name. + * This parameter can be used to check which one of set of template + * files is available. + * @return string or boolean Source template path if template exists or $return_default is + * true. False if template does not exist and $return_default is false + */ + public function locate($files, $return_default = false, $return_full_path = true) + { + // add template path prefix + $templates = array(); + if (is_string($files)) + { + $templates[] = $this->template_path . $files; + } + else + { + foreach ($files as $file) + { + $templates[] = $this->template_path . $file; + } + } + + // use resource locator to find files + return $this->locator->get_first_file_location($templates, $return_default, $return_full_path); + } + + /** + * Include JS file + * + * @param string $file file name + * @param bool $locate True if file needs to be located + * @param bool $relative True if path is relative to phpBB root directory. Ignored if $locate == true + */ + public function _js_include($file, $locate = false, $relative = false) + { + // Locate file + if ($locate) + { + $located = $this->locator->get_first_file_location(array($file), false, true); + if ($located) + { + $file = $located; + } + } + else if ($relative) + { + $file = $this->phpbb_root_path . $file; + } + + $file .= (strpos($file, '?') === false) ? '?' : '&'; + $file .= 'assets_version=' . $this->config['assets_version']; + + // Add HTML code + $code = ''; + $this->context->append_var('SCRIPTS', $code); + } +} From 46cb0fb068feeb89c939d8b145808d2a786de8dd Mon Sep 17 00:00:00 2001 From: David King Date: Tue, 13 Nov 2012 10:57:24 -0500 Subject: [PATCH 0987/1142] [feature/controller] Removed another empty construct method PHPBB3-10864 --- tests/controller/includes/controller/foo.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/controller/includes/controller/foo.php b/tests/controller/includes/controller/foo.php index cd1c4849cb..04576e16c4 100644 --- a/tests/controller/includes/controller/foo.php +++ b/tests/controller/includes/controller/foo.php @@ -4,13 +4,6 @@ use Symfony\Component\HttpFoundation\Response; class phpbb_controller_foo { - /** - * Constructor - */ - public function __construct() - { - } - /** * Bar method * From 5877bf1b1b3dc2899543d99859e3bde6e70d8647 Mon Sep 17 00:00:00 2001 From: David King Date: Tue, 13 Nov 2012 11:02:01 -0500 Subject: [PATCH 0988/1142] [feature/controller] Update helper service given constructor change PHPBB3-10864 --- phpBB/config/services.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 54d347debe..184ebb80ee 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -47,7 +47,10 @@ services: controller.helper: class: phpbb_controller_helper arguments: - - @service_container + - @template + - @user + - %core.root_path% + - .%core.php_ext% controller.resolver: class: phpbb_controller_resolver @@ -64,7 +67,7 @@ services: controller.provider: class: phpbb_controller_provider - + cron.task_collection: class: phpbb_di_service_collection arguments: @@ -113,7 +116,7 @@ services: - %core.root_path% - .%core.php_ext% - @cache.driver - + ext.finder: class: phpbb_extension_finder arguments: From d3aa8823b21990634f8b74676ac301739ddfc58b Mon Sep 17 00:00:00 2001 From: David King Date: Wed, 14 Nov 2012 15:42:13 -0500 Subject: [PATCH 0989/1142] [feature/controller] Use a dumped url matcher class to improve performance PHPBB3-10864 --- phpBB/config/services.yml | 21 ++------ .../includes/controller/route_collection.php | 36 ------------- phpBB/includes/event/kernel_subscriber.php | 52 ++++++++++++++++++- phpBB/includes/functions.php | 52 +++++++++++++++++++ 4 files changed, 106 insertions(+), 55 deletions(-) delete mode 100644 phpBB/includes/controller/route_collection.php diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 184ebb80ee..4fe9fa52eb 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -123,7 +123,7 @@ services: - @ext.manager - %core.root_path% - @cache.driver - - .%core.php_ext% + - %core.php_ext% - _ext_finder http_kernel: @@ -137,22 +137,15 @@ services: arguments: - @template - @user + - @ext.finder + - %core.root_path% + - .%core.php_ext% tags: - { name: kernel.event_subscriber } request: class: phpbb_request - request.context: - class: Symfony\Component\Routing\RequestContext - - router_listener: - class: Symfony\Component\HttpKernel\EventListener\RouterListener - arguments: - - @url_matcher - tags: - - { name: kernel.event_subscriber } - style: class: phpbb_style arguments: @@ -189,11 +182,5 @@ services: template_context: class: phpbb_template_context - url_matcher: - class: Symfony\Component\Routing\Matcher\UrlMatcher - arguments: - - @controller.route_collection - - @request.context - user: class: phpbb_user diff --git a/phpBB/includes/controller/route_collection.php b/phpBB/includes/controller/route_collection.php deleted file mode 100644 index e6c7d3b543..0000000000 --- a/phpBB/includes/controller/route_collection.php +++ /dev/null @@ -1,36 +0,0 @@ -addCollection($provider->get_paths($finder)->find()); - } -} diff --git a/phpBB/includes/event/kernel_subscriber.php b/phpBB/includes/event/kernel_subscriber.php index 9737d9bc23..79ee4f4dc5 100644 --- a/phpBB/includes/event/kernel_subscriber.php +++ b/phpBB/includes/event/kernel_subscriber.php @@ -18,8 +18,11 @@ if (!defined('IN_PHPBB')) use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\Routing\RequestContext; class phpbb_event_kernel_subscriber implements EventSubscriberInterface { @@ -35,16 +38,40 @@ class phpbb_event_kernel_subscriber implements EventSubscriberInterface */ protected $user; + /** + * Extension finder object + * @var phpbb_extension_finder + */ + protected $finder; + + /** + * PHP extension + * @var string + */ + protected $php_ext; + + /** + * Root path + * @var string + */ + protected $root_path; + /** * Construct method * * @param phpbb_template $template Template object * @param phpbb_user $user User object + * @param phpbb_extension_finder $finder Extension finder object + * @param string $root_path Root path + * @param string $php_ext PHP extension */ - public function __construct(phpbb_template $template, phpbb_user $user) + public function __construct(phpbb_template $template, phpbb_user $user, phpbb_extension_finder $finder, $root_path, $php_ext) { $this->template = $template; $this->user = $user; + $this->finder = $finder; + $this->root_path = $root_path; + $this->php_ext = $php_ext; } /** @@ -81,12 +108,33 @@ class phpbb_event_kernel_subscriber implements EventSubscriberInterface page_footer(true, false, false); - $event->setResponse(new Response($this->template->return_display('body'), 404)); + $event->setResponse(new Response($this->template->assign_display('body'), 404)); + } + + /** + * This listener is run when the KernelEvents::REQUEST event is triggered + * + * This is responsible for setting up the routing information + * + * @param GetResponseEvent $event + * @return null + */ + public function on_kernel_request(GetResponseEvent $event) + { + $request = $event->getRequest(); + $context = new RequestContext(); + $context->fromRequest($request); + + $matcher = phpbb_create_url_matcher($this->finder, $context, $this->root_path, $this->php_ext); + + $router_listener = new RouterListener($matcher, $context); + $router_listener->onKernelRequest($event); } public static function getSubscribedEvents() { return array( + KernelEvents::REQUEST => 'on_kernel_request', KernelEvents::TERMINATE => 'on_kernel_terminate', KernelEvents::EXCEPTION => 'on_kernel_exception', ); diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index fb05b74cd3..7cf5611dca 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -7,6 +7,9 @@ * */ +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\RequestContext; + /** * @ignore */ @@ -5444,3 +5447,52 @@ function phpbb_to_numeric($input) { return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; } + +/** +* Create and/or return the cached phpbb_url_matcher class +* +* If the class already exists, it instantiates it +* +* @param phpbb_extension_finder $finder Extension finder +* @param RequestContext $context Symfony RequestContext object +* @param string $root_path Root path +* @param string $php_ext PHP extension +* @return phpbb_url_matcher +*/ +function phpbb_create_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) +{ + $matcher = phpbb_load_url_matcher($finder, $context, $root_path, $php_ext); + if ($matcher === false) + { + $provider = new phpbb_controller_provider(); + $dumper = new PhpMatcherDumper($provider->get_paths($finder)->find()); + $cached_url_matcher_dump = $dumper->dump(array( + 'class' => 'phpbb_url_matcher', + )); + + file_put_contents($root_path . 'cache/url_matcher' . $php_ext, $cached_url_matcher_dump); + return phpbb_load_url_matcher($finder, $context, $root_path, $php_ext); + } + + return $matcher; +} + +/** +* Load the cached phpbb_url_matcher class +* +* @param phpbb_extension_finder $finder Extension finder +* @param RequestContext $context Symfony RequestContext object +* @param string $root_path Root path +* @param string $php_ext PHP extension +* @return phpbb_url_matcher|bool False if the file doesn't exist +*/ +function phpbb_load_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) +{ + if (file_exists($root_path . 'cache/url_matcher' . $php_ext)) + { + include($root_path . 'cache/url_matcher' . $php_ext); + return new phpbb_url_matcher($context); + } + + return false; +} From 269a56e2c3817cc27f2b21b9eb864c4f2452bc13 Mon Sep 17 00:00:00 2001 From: David King Date: Wed, 14 Nov 2012 15:52:41 -0500 Subject: [PATCH 0990/1142] [feature/controller] Undo removal of period in ext.finder service definition PHPBB3-10864 --- phpBB/config/services.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 4fe9fa52eb..91610a3527 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -123,7 +123,7 @@ services: - @ext.manager - %core.root_path% - @cache.driver - - %core.php_ext% + - .%core.php_ext% - _ext_finder http_kernel: From 196c2d4bc346ab6a31fd0b752c788e37cf39459d Mon Sep 17 00:00:00 2001 From: David King Date: Wed, 14 Nov 2012 15:56:07 -0500 Subject: [PATCH 0991/1142] [feature/controller] Move new functions to their own file PHPBB3-10864 --- phpBB/includes/functions.php | 49 ----------------- phpBB/includes/functions_url_matcher.php | 68 ++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 49 deletions(-) create mode 100644 phpBB/includes/functions_url_matcher.php diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 7cf5611dca..88ce142195 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5447,52 +5447,3 @@ function phpbb_to_numeric($input) { return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; } - -/** -* Create and/or return the cached phpbb_url_matcher class -* -* If the class already exists, it instantiates it -* -* @param phpbb_extension_finder $finder Extension finder -* @param RequestContext $context Symfony RequestContext object -* @param string $root_path Root path -* @param string $php_ext PHP extension -* @return phpbb_url_matcher -*/ -function phpbb_create_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) -{ - $matcher = phpbb_load_url_matcher($finder, $context, $root_path, $php_ext); - if ($matcher === false) - { - $provider = new phpbb_controller_provider(); - $dumper = new PhpMatcherDumper($provider->get_paths($finder)->find()); - $cached_url_matcher_dump = $dumper->dump(array( - 'class' => 'phpbb_url_matcher', - )); - - file_put_contents($root_path . 'cache/url_matcher' . $php_ext, $cached_url_matcher_dump); - return phpbb_load_url_matcher($finder, $context, $root_path, $php_ext); - } - - return $matcher; -} - -/** -* Load the cached phpbb_url_matcher class -* -* @param phpbb_extension_finder $finder Extension finder -* @param RequestContext $context Symfony RequestContext object -* @param string $root_path Root path -* @param string $php_ext PHP extension -* @return phpbb_url_matcher|bool False if the file doesn't exist -*/ -function phpbb_load_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) -{ - if (file_exists($root_path . 'cache/url_matcher' . $php_ext)) - { - include($root_path . 'cache/url_matcher' . $php_ext); - return new phpbb_url_matcher($context); - } - - return false; -} diff --git a/phpBB/includes/functions_url_matcher.php b/phpBB/includes/functions_url_matcher.php new file mode 100644 index 0000000000..0a6e90703c --- /dev/null +++ b/phpBB/includes/functions_url_matcher.php @@ -0,0 +1,68 @@ +get_paths($finder)->find()); + $cached_url_matcher_dump = $dumper->dump(array( + 'class' => 'phpbb_url_matcher', + )); + + file_put_contents($root_path . 'cache/url_matcher' . $php_ext, $cached_url_matcher_dump); + return phpbb_load_url_matcher($finder, $context, $root_path, $php_ext); + } + + return $matcher; +} + +/** +* Load the cached phpbb_url_matcher class +* +* @param phpbb_extension_finder $finder Extension finder +* @param RequestContext $context Symfony RequestContext object +* @param string $root_path Root path +* @param string $php_ext PHP extension +* @return phpbb_url_matcher|bool False if the file doesn't exist +*/ +function phpbb_load_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) +{ + if (file_exists($root_path . 'cache/url_matcher' . $php_ext)) + { + include($root_path . 'cache/url_matcher' . $php_ext); + return new phpbb_url_matcher($context); + } + + return false; +} From 4cb9ec522c7007b99eb5ef44cb1bfdb369478aff Mon Sep 17 00:00:00 2001 From: David King Date: Wed, 14 Nov 2012 16:06:12 -0500 Subject: [PATCH 0992/1142] [feature/controller] Separate Kernel listeners into their own classes PHPBB3-10864 --- phpBB/config/services.yml | 19 ++++- .../event/kernel_exception_subscriber.php | 79 +++++++++++++++++++ ...iber.php => kernel_request_subscriber.php} | 64 ++------------- .../event/kernel_terminate_subscriber.php | 43 ++++++++++ 4 files changed, 143 insertions(+), 62 deletions(-) create mode 100644 phpBB/includes/event/kernel_exception_subscriber.php rename phpBB/includes/event/{kernel_subscriber.php => kernel_request_subscriber.php} (50%) create mode 100644 phpBB/includes/event/kernel_terminate_subscriber.php diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 91610a3527..37e6c0b5df 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -132,17 +132,28 @@ services: - @dispatcher - @controller.resolver - kernel_event_subscriber: - class: phpbb_event_kernel_subscriber + kernel_request_subscriber: + class: phpbb_event_kernel_request_subscriber arguments: - - @template - - @user - @ext.finder - %core.root_path% - .%core.php_ext% tags: - { name: kernel.event_subscriber } + kernel_exception_subscriber: + class: phpbb_event_kernel_exception_subscriber + arguments: + - @template + - @user + tags: + - { name: kernel.event_subscriber } + + kernel_terminate_subscriber: + class: phpbb_event_kernel_terminate_subscriber + tags: + - { name: kernel.event_subscriber } + request: class: phpbb_request diff --git a/phpBB/includes/event/kernel_exception_subscriber.php b/phpBB/includes/event/kernel_exception_subscriber.php new file mode 100644 index 0000000000..cd6ea40c70 --- /dev/null +++ b/phpBB/includes/event/kernel_exception_subscriber.php @@ -0,0 +1,79 @@ +template = $template; + $this->user = $user; + } + + /** + * This listener is run when the KernelEvents::EXCEPTION event is triggered + * + * @param GetResponseForExceptionEvent $event + * @return null + */ + public function on_kernel_exception(GetResponseForExceptionEvent $event) + { + page_header($this->user->lang('INFORMATION')); + + $this->template->assign_vars(array( + 'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), + 'MESSAGE_TEXT' => $event->getException()->getMessage(), + )); + + $this->template->set_filenames(array( + 'body' => 'message_body.html', + )); + + page_footer(true, false, false); + + $event->setResponse(new Response($this->template->assign_display('body'), 404)); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => 'on_kernel_exception', + ); + } +} diff --git a/phpBB/includes/event/kernel_subscriber.php b/phpBB/includes/event/kernel_request_subscriber.php similarity index 50% rename from phpBB/includes/event/kernel_subscriber.php rename to phpBB/includes/event/kernel_request_subscriber.php index 79ee4f4dc5..98079acabb 100644 --- a/phpBB/includes/event/kernel_subscriber.php +++ b/phpBB/includes/event/kernel_request_subscriber.php @@ -17,27 +17,12 @@ if (!defined('IN_PHPBB')) use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\Event\PostResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\EventListener\RouterListener; use Symfony\Component\Routing\RequestContext; -class phpbb_event_kernel_subscriber implements EventSubscriberInterface +class phpbb_event_kernel_request_subscriber implements EventSubscriberInterface { - /** - * Template object - * @var phpbb_template - */ - protected $template; - - /** - * User object - * @var phpbb_user - */ - protected $user; - /** * Extension finder object * @var phpbb_extension_finder @@ -59,13 +44,11 @@ class phpbb_event_kernel_subscriber implements EventSubscriberInterface /** * Construct method * - * @param phpbb_template $template Template object - * @param phpbb_user $user User object * @param phpbb_extension_finder $finder Extension finder object * @param string $root_path Root path * @param string $php_ext PHP extension */ - public function __construct(phpbb_template $template, phpbb_user $user, phpbb_extension_finder $finder, $root_path, $php_ext) + public function __construct(phpbb_extension_finder $finder, $root_path, $php_ext) { $this->template = $template; $this->user = $user; @@ -74,43 +57,6 @@ class phpbb_event_kernel_subscriber implements EventSubscriberInterface $this->php_ext = $php_ext; } - /** - * This listener is run when the KernelEvents::TERMINATE event is triggered - * This comes after a Response has been sent to the server; this is - * primarily cleanup stuff. - * - * @param PostResponseEvent $event - * @return null - */ - public function on_kernel_terminate(PostResponseEvent $event) - { - exit_handler(); - } - - /** - * This listener is run when the KernelEvents::EXCEPTION event is triggered - * - * @param GetResponseForExceptionEvent $event - * @return null - */ - public function on_kernel_exception(GetResponseForExceptionEvent $event) - { - page_header($this->user->lang('INFORMATION')); - - $this->template->assign_vars(array( - 'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), - 'MESSAGE_TEXT' => $event->getException()->getMessage(), - )); - - $this->template->set_filenames(array( - 'body' => 'message_body.html', - )); - - page_footer(true, false, false); - - $event->setResponse(new Response($this->template->assign_display('body'), 404)); - } - /** * This listener is run when the KernelEvents::REQUEST event is triggered * @@ -125,6 +71,10 @@ class phpbb_event_kernel_subscriber implements EventSubscriberInterface $context = new RequestContext(); $context->fromRequest($request); + if (!function_exists('phpbb_create_url_matcher')) + { + include($this->root_path . 'includes/functions_url_matcher' . $this->php_ext); + } $matcher = phpbb_create_url_matcher($this->finder, $context, $this->root_path, $this->php_ext); $router_listener = new RouterListener($matcher, $context); @@ -135,8 +85,6 @@ class phpbb_event_kernel_subscriber implements EventSubscriberInterface { return array( KernelEvents::REQUEST => 'on_kernel_request', - KernelEvents::TERMINATE => 'on_kernel_terminate', - KernelEvents::EXCEPTION => 'on_kernel_exception', ); } } diff --git a/phpBB/includes/event/kernel_terminate_subscriber.php b/phpBB/includes/event/kernel_terminate_subscriber.php new file mode 100644 index 0000000000..1eaf890e42 --- /dev/null +++ b/phpBB/includes/event/kernel_terminate_subscriber.php @@ -0,0 +1,43 @@ + 'on_kernel_terminate', + ); + } +} From fa43edd8778dffd21146350f1749fad5c0755fb7 Mon Sep 17 00:00:00 2001 From: David King Date: Wed, 14 Nov 2012 16:42:52 -0500 Subject: [PATCH 0993/1142] [feature/controller] Further separate url matcher functionality PHPBB3-10864 --- .../event/kernel_request_subscriber.php | 6 +- phpBB/includes/functions_url_matcher.php | 89 +++++++++++++------ 2 files changed, 65 insertions(+), 30 deletions(-) diff --git a/phpBB/includes/event/kernel_request_subscriber.php b/phpBB/includes/event/kernel_request_subscriber.php index 98079acabb..85c8c5b173 100644 --- a/phpBB/includes/event/kernel_request_subscriber.php +++ b/phpBB/includes/event/kernel_request_subscriber.php @@ -50,8 +50,6 @@ class phpbb_event_kernel_request_subscriber implements EventSubscriberInterface */ public function __construct(phpbb_extension_finder $finder, $root_path, $php_ext) { - $this->template = $template; - $this->user = $user; $this->finder = $finder; $this->root_path = $root_path; $this->php_ext = $php_ext; @@ -71,11 +69,11 @@ class phpbb_event_kernel_request_subscriber implements EventSubscriberInterface $context = new RequestContext(); $context->fromRequest($request); - if (!function_exists('phpbb_create_url_matcher')) + if (!function_exists('phpbb_load_url_matcher')) { include($this->root_path . 'includes/functions_url_matcher' . $this->php_ext); } - $matcher = phpbb_create_url_matcher($this->finder, $context, $this->root_path, $this->php_ext); + $matcher = phpbb_get_url_matcher($this->finder, $context, $this->root_path, $this->php_ext); $router_listener = new RouterListener($matcher, $context); $router_listener->onKernelRequest($event); diff --git a/phpBB/includes/functions_url_matcher.php b/phpBB/includes/functions_url_matcher.php index 0a6e90703c..f628337dee 100644 --- a/phpBB/includes/functions_url_matcher.php +++ b/phpBB/includes/functions_url_matcher.php @@ -8,6 +8,7 @@ */ use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; /** @@ -19,50 +20,86 @@ if (!defined('IN_PHPBB')) } /** -* Create and/or return the cached phpbb_url_matcher class -* -* If the class already exists, it instantiates it +* Create a new UrlMatcher class and dump it into the cache file * * @param phpbb_extension_finder $finder Extension finder * @param RequestContext $context Symfony RequestContext object * @param string $root_path Root path * @param string $php_ext PHP extension -* @return phpbb_url_matcher +* @return null */ -function phpbb_create_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) +function phpbb_get_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) { - $matcher = phpbb_load_url_matcher($finder, $context, $root_path, $php_ext); - if ($matcher === false) + if (defined('DEBUG')) { - $provider = new phpbb_controller_provider(); - $dumper = new PhpMatcherDumper($provider->get_paths($finder)->find()); - $cached_url_matcher_dump = $dumper->dump(array( - 'class' => 'phpbb_url_matcher', - )); - - file_put_contents($root_path . 'cache/url_matcher' . $php_ext, $cached_url_matcher_dump); - return phpbb_load_url_matcher($finder, $context, $root_path, $php_ext); + return phpbb_create_url_matcher($finder, $context); } - return $matcher; + if (phpbb_url_matcher_dumped($root_path, $php_ext) === false) + { + phpbb_create_dumped_url_matcher($finder, $context, $root_path, $php_ext); + } + + return phpbb_load_url_matcher($context, $root_path, $php_ext); +} + +/** +* Create a new UrlMatcher class and dump it into the cache file +* +* @param phpbb_extension_finder $finder Extension finder +* @param RequestContext $context Symfony RequestContext object +* @param string $root_path Root path +* @param string $php_ext PHP extension +* @return null +*/ +function phpbb_create_dumped_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) +{ + $provider = new phpbb_controller_provider(); + $dumper = new PhpMatcherDumper($provider->get_paths($finder)->find()); + $cached_url_matcher_dump = $dumper->dump(array( + 'class' => 'phpbb_url_matcher', + )); + + file_put_contents($root_path . 'cache/url_matcher' . $php_ext, $cached_url_matcher_dump); +} + +/** +* Create a non-cached UrlMatcher +* +* @param phpbb_extension_finder $finder Extension finder +* @param RequestContext $context Symfony RequestContext object +* @return UrlMatcher +*/ +function phpbb_create_url_matcher(phpbb_extension_finder $finder, RequestContext $context) +{ + $provider = new phpbb_controller_provider(); + return new UrlMatcher($provider->get_paths($finder)->find(), $context); } /** * Load the cached phpbb_url_matcher class * -* @param phpbb_extension_finder $finder Extension finder * @param RequestContext $context Symfony RequestContext object * @param string $root_path Root path * @param string $php_ext PHP extension -* @return phpbb_url_matcher|bool False if the file doesn't exist +* @return phpbb_url_matcher */ -function phpbb_load_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) +function phpbb_load_url_matcher(RequestContext $context, $root_path, $php_ext) { - if (file_exists($root_path . 'cache/url_matcher' . $php_ext)) - { - include($root_path . 'cache/url_matcher' . $php_ext); - return new phpbb_url_matcher($context); - } - - return false; + require($root_path . 'cache/url_matcher' . $php_ext); + return new phpbb_url_matcher($context); +} + +/** +* Determine whether we have our dumped URL matcher +* +* The class is automatically dumped to the cache directory +* +* @param string $root_path Root path +* @param string $php_ext PHP extension +* @return bool True if it exists, false if not +*/ +function phpbb_url_matcher_dumped($root_path, $php_ext) +{ + return file_exists($root_path . 'cache/url_matcher' . $php_ext); } From c54c3ee422a9bfb0878aecf80cbb298e230e4fd4 Mon Sep 17 00:00:00 2001 From: David King Date: Wed, 14 Nov 2012 17:04:45 -0500 Subject: [PATCH 0994/1142] [feature/controller] A few minor nitpickings PHPBB3-10864 --- phpBB/includes/functions_url_matcher.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions_url_matcher.php b/phpBB/includes/functions_url_matcher.php index f628337dee..782acc4c20 100644 --- a/phpBB/includes/functions_url_matcher.php +++ b/phpBB/includes/functions_url_matcher.php @@ -35,7 +35,7 @@ function phpbb_get_url_matcher(phpbb_extension_finder $finder, RequestContext $c return phpbb_create_url_matcher($finder, $context); } - if (phpbb_url_matcher_dumped($root_path, $php_ext) === false) + if (!phpbb_url_matcher_dumped($root_path, $php_ext)) { phpbb_create_dumped_url_matcher($finder, $context, $root_path, $php_ext); } @@ -55,7 +55,8 @@ function phpbb_get_url_matcher(phpbb_extension_finder $finder, RequestContext $c function phpbb_create_dumped_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) { $provider = new phpbb_controller_provider(); - $dumper = new PhpMatcherDumper($provider->get_paths($finder)->find()); + $routes = $provider->get_paths($finder)->find(); + $dumper = new PhpMatcherDumper($routes); $cached_url_matcher_dump = $dumper->dump(array( 'class' => 'phpbb_url_matcher', )); From aead33432ae67857009f9570a5ec720d86f7575b Mon Sep 17 00:00:00 2001 From: David King Date: Wed, 14 Nov 2012 17:14:21 -0500 Subject: [PATCH 0995/1142] [feature/controller] Remove dumped container when cache is purged PHPBB3-10864 --- phpBB/includes/cache/driver/file.php | 1 + phpBB/includes/cache/driver/memory.php | 1 + 2 files changed, 2 insertions(+) diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php index 32bdb1918a..5014ba18af 100644 --- a/phpBB/includes/cache/driver/file.php +++ b/phpBB/includes/cache/driver/file.php @@ -215,6 +215,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base while (($entry = readdir($dir)) !== false) { if (strpos($entry, 'container_') !== 0 && + strpos($entry, 'url_matcher') !== 0 && strpos($entry, 'sql_') !== 0 && strpos($entry, 'data_') !== 0 && strpos($entry, 'ctpl_') !== 0 && diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php index 1ea9a3e9e7..f6c42c0ea6 100644 --- a/phpBB/includes/cache/driver/memory.php +++ b/phpBB/includes/cache/driver/memory.php @@ -163,6 +163,7 @@ abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base while (($entry = readdir($dir)) !== false) { if (strpos($entry, 'container_') !== 0 && + strpos($entry, 'url_matcher') !== 0 && strpos($entry, 'sql_') !== 0 && strpos($entry, 'data_') !== 0 && strpos($entry, 'ctpl_') !== 0 && From b4eff4f06acce78536a392b733f2438f4d1d1c52 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 13:54:41 -0500 Subject: [PATCH 0996/1142] [feature/controller] Remove URL Base from helper class I had forgotten that the container sends the same instance of objects to all services that request it, so in this case all controllers would share the same base url path, which is not desired. PHPBB3-10864 --- phpBB/includes/controller/helper.php | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/phpBB/includes/controller/helper.php b/phpBB/includes/controller/helper.php index 8bb4427382..6b697c7e46 100644 --- a/phpBB/includes/controller/helper.php +++ b/phpBB/includes/controller/helper.php @@ -47,12 +47,6 @@ class phpbb_controller_helper */ protected $php_ext; - /** - * Base URL - * @var array - */ - protected $url_base; - /** * Constructor * @@ -101,21 +95,7 @@ class phpbb_controller_helper */ public function url(array $url_parts, $query = '') { - return append_sid($this->root_path . $this->url_base . implode('/', $url_parts), $query); - } - - /** - * Set base to prepend to urls generated by url() - * This allows extensions to have a certain 'directory' under which - * all their pages are served, but not have to type it every time - * - * @param array $url_parts Each array element is a 'folder' - * i.e. array('my', 'ext') maps to ./app.php/my/ext - * @return null - */ - public function set_url_base(array $url_parts) - { - $this->url_base = !empty($url_parts) ? implode('/', $url_parts) . '/' : ''; + return append_sid($this->root_path . implode('/', $url_parts), $query); } /** From db1d49d559a337f7266a9e9f0cfaf3eb025b0ed1 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 13:59:30 -0500 Subject: [PATCH 0997/1142] [feature/controller] Rename get_paths to import_paths_from_finder Also removed unused variable from url_matcher function PHPBB3-10864 --- phpBB/includes/controller/provider.php | 20 ++++---------------- phpBB/includes/functions_url_matcher.php | 10 +++++----- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/phpBB/includes/controller/provider.php b/phpBB/includes/controller/provider.php index 25deedb5d1..b2a5b9f6b2 100644 --- a/phpBB/includes/controller/provider.php +++ b/phpBB/includes/controller/provider.php @@ -39,7 +39,7 @@ class phpbb_controller_provider */ public function __construct($routing_paths = array()) { - $this->set_paths($routing_paths); + $this->routing_paths = $routing_paths; } /** @@ -48,28 +48,16 @@ class phpbb_controller_provider * * @return The current instance of this object for method chaining */ - public function get_paths(phpbb_extension_finder $finder) + public function import_paths_from_finder(phpbb_extension_finder $finder) { // We hardcode the path to the core config directory // because the finder cannot find it - $this->set_paths(array_merge(array('config'), array_map('dirname', array_keys($finder + $this->routing_paths = array_merge(array('config'), array_map('dirname', array_keys($finder ->directory('config') ->prefix('routing') ->suffix('yml') ->find() - )))); - - return $this; - } - - /** - * Set the $routing_paths property with a given list of paths - * - * @return The current instance of this object for method chaining - */ - public function set_paths(array $paths) - { - $this->routing_paths = $paths; + ))); return $this; } diff --git a/phpBB/includes/functions_url_matcher.php b/phpBB/includes/functions_url_matcher.php index 782acc4c20..7280cb74eb 100644 --- a/phpBB/includes/functions_url_matcher.php +++ b/phpBB/includes/functions_url_matcher.php @@ -37,7 +37,7 @@ function phpbb_get_url_matcher(phpbb_extension_finder $finder, RequestContext $c if (!phpbb_url_matcher_dumped($root_path, $php_ext)) { - phpbb_create_dumped_url_matcher($finder, $context, $root_path, $php_ext); + phpbb_create_dumped_url_matcher($finder, $root_path, $php_ext); } return phpbb_load_url_matcher($context, $root_path, $php_ext); @@ -47,15 +47,14 @@ function phpbb_get_url_matcher(phpbb_extension_finder $finder, RequestContext $c * Create a new UrlMatcher class and dump it into the cache file * * @param phpbb_extension_finder $finder Extension finder -* @param RequestContext $context Symfony RequestContext object * @param string $root_path Root path * @param string $php_ext PHP extension * @return null */ -function phpbb_create_dumped_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext) +function phpbb_create_dumped_url_matcher(phpbb_extension_finder $finder, $root_path, $php_ext) { $provider = new phpbb_controller_provider(); - $routes = $provider->get_paths($finder)->find(); + $routes = $provider->import_paths_from_finder($finder)->find(); $dumper = new PhpMatcherDumper($routes); $cached_url_matcher_dump = $dumper->dump(array( 'class' => 'phpbb_url_matcher', @@ -74,7 +73,8 @@ function phpbb_create_dumped_url_matcher(phpbb_extension_finder $finder, Request function phpbb_create_url_matcher(phpbb_extension_finder $finder, RequestContext $context) { $provider = new phpbb_controller_provider(); - return new UrlMatcher($provider->get_paths($finder)->find(), $context); + $routes = $provider->import_paths_from_finder($finder)->find(); + return new UrlMatcher($routes, $context); } /** From 8e480a87253fd2e42af3fc4daaed2f49b9ae65c5 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 14:48:45 -0500 Subject: [PATCH 0998/1142] [feature/controller] Move include to app.php PHPBB3-10864 --- phpBB/app.php | 1 + phpBB/includes/event/kernel_request_subscriber.php | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/phpBB/app.php b/phpBB/app.php index 9ff9069104..a2dbb1ae5b 100644 --- a/phpBB/app.php +++ b/phpBB/app.php @@ -17,6 +17,7 @@ define('IN_PHPBB', true); $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; $phpEx = substr(strrchr(__FILE__, '.'), 1); include($phpbb_root_path . 'common.' . $phpEx); +include($this->root_path . 'includes/functions_url_matcher' . $this->php_ext); // Start session management $user->session_begin(); diff --git a/phpBB/includes/event/kernel_request_subscriber.php b/phpBB/includes/event/kernel_request_subscriber.php index 85c8c5b173..afb8464f80 100644 --- a/phpBB/includes/event/kernel_request_subscriber.php +++ b/phpBB/includes/event/kernel_request_subscriber.php @@ -69,12 +69,7 @@ class phpbb_event_kernel_request_subscriber implements EventSubscriberInterface $context = new RequestContext(); $context->fromRequest($request); - if (!function_exists('phpbb_load_url_matcher')) - { - include($this->root_path . 'includes/functions_url_matcher' . $this->php_ext); - } $matcher = phpbb_get_url_matcher($this->finder, $context, $this->root_path, $this->php_ext); - $router_listener = new RouterListener($matcher, $context); $router_listener->onKernelRequest($event); } From 91ce6e8b16abb831e1e18b73cf857a257a59e432 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 14:59:14 -0500 Subject: [PATCH 0999/1142] [feature/controller] Turn off URL rewriting by default, add comments for usage PHPBB3-10864 --- phpBB/.htaccess | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/phpBB/.htaccess b/phpBB/.htaccess index 68021177f2..1713f8e522 100644 --- a/phpBB/.htaccess +++ b/phpBB/.htaccess @@ -1,7 +1,11 @@ Options +FollowSymLinks -RewriteEngine on +# +# Uncomment the following line if you will be using any of the URL +# rewriting below. +# +#RewriteEngine on # # Uncomment the statement below if you want to make use of @@ -10,9 +14,15 @@ RewriteEngine on # #RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -RewriteRule ^(.*)$ app.php [QSA,L] +# +# Uncomment the following 3 lines if you want to rewrite URLs passed through +# the front controller to not use app.php in the actual URL. In other words, +# an controller that would by default be accessed at /app.php/my/controller +# can now be accessed at /my/controller directly. +# +#RewriteCond %{REQUEST_FILENAME} !-f +#RewriteCond %{REQUEST_FILENAME} !-d +#RewriteRule ^(.*)$ app.php [QSA,L] From db071d68541d132f5b06da652a2214b664552b1e Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 15:05:52 -0500 Subject: [PATCH 1000/1142] [feature/controller] Fix use of $this in global scope PHPBB3-10864 --- phpBB/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/app.php b/phpBB/app.php index a2dbb1ae5b..a7d645cb09 100644 --- a/phpBB/app.php +++ b/phpBB/app.php @@ -17,7 +17,7 @@ define('IN_PHPBB', true); $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; $phpEx = substr(strrchr(__FILE__, '.'), 1); include($phpbb_root_path . 'common.' . $phpEx); -include($this->root_path . 'includes/functions_url_matcher' . $this->php_ext); +include($phpbb_root_path . 'includes/functions_url_matcher' . $phpEx); // Start session management $user->session_begin(); From d0269629dcee2dda176807bdd944415d1713db7b Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 15:20:47 -0500 Subject: [PATCH 1001/1142] [feature/controller] Documentation about Symlinks in .htaccess PHPBB3-10864 --- phpBB/.htaccess | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/phpBB/.htaccess b/phpBB/.htaccess index 1713f8e522..9f635dba57 100644 --- a/phpBB/.htaccess +++ b/phpBB/.htaccess @@ -1,5 +1,3 @@ -Options +FollowSymLinks - # # Uncomment the following line if you will be using any of the URL @@ -23,6 +21,12 @@ Options +FollowSymLinks #RewriteCond %{REQUEST_FILENAME} !-f #RewriteCond %{REQUEST_FILENAME} !-d #RewriteRule ^(.*)$ app.php [QSA,L] + +# +# On Windows, you must also uncomment the following line so that SymLinks +# are followed. +# +#Options +FollowSymLinks From 14f44c17ad76878ed540cd5dd56a3a62b30dbd15 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 16:14:11 -0500 Subject: [PATCH 1002/1142] [feature/controller] phpbb_controller_exception instead of RuntimeException PHPBB3-10864 --- phpBB/includes/controller/exception.php | 24 ++++++++++++++++++++++++ phpBB/includes/controller/resolver.php | 11 ++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 phpBB/includes/controller/exception.php diff --git a/phpBB/includes/controller/exception.php b/phpBB/includes/controller/exception.php new file mode 100644 index 0000000000..da2fefc600 --- /dev/null +++ b/phpBB/includes/controller/exception.php @@ -0,0 +1,24 @@ +user->lang['CONTROLLER_NOT_SPECIFIED']); + throw new phpbb_controller_exception($this->user->lang['CONTROLLER_NOT_SPECIFIED']); } // Require a method name along with the service name if (stripos($controller, ':') === false) { - throw new RuntimeException($this->user->lang['CONTROLLER_METHOD_NOT_SPECIFIED']); + throw new phpbb_controller_exception($this->user->lang['CONTROLLER_METHOD_NOT_SPECIFIED']); } list($service, $method) = explode(':', $controller); if (!$this->container->has($service)) { - throw new RuntimeException($this->user->lang('CONTROLLER_SERVICE_UNDEFINED', $service)); + throw new phpbb_controller_exception($this->user->lang('CONTROLLER_SERVICE_UNDEFINED', $service)); } $controller_object = $this->container->get($service); @@ -92,6 +92,7 @@ class phpbb_controller_resolver implements ControllerResolverInterface * @param Symfony\Component\HttpFoundation\Request $request Symfony Request object * @param string $controller Controller class name * @return bool False + * @throws phpbb_controller_exception */ public function getArguments(Request $request, $controller) { @@ -114,7 +115,7 @@ class phpbb_controller_resolver implements ControllerResolverInterface } else { - throw new RuntimeException($user->lang('CONTROLLER_ARGUMENT_VALUE_MISSING', $param->getPosition() + 1, get_class($object) . ':' . $method, $param->name)); + throw new phpbb_controller_exception($user->lang('CONTROLLER_ARGUMENT_VALUE_MISSING', $param->getPosition() + 1, get_class($object) . ':' . $method, $param->name)); } } From 235b0194f140a369137bd4d92bfd1b10d5e08787 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 16:16:25 -0500 Subject: [PATCH 1003/1142] [feature/controller] Update wording in .htaccess documentation comments PHPBB3-10864 --- phpBB/.htaccess | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/.htaccess b/phpBB/.htaccess index 9f635dba57..d61ef7b893 100644 --- a/phpBB/.htaccess +++ b/phpBB/.htaccess @@ -15,8 +15,8 @@ # # Uncomment the following 3 lines if you want to rewrite URLs passed through # the front controller to not use app.php in the actual URL. In other words, -# an controller that would by default be accessed at /app.php/my/controller -# can now be accessed at /my/controller directly. +# a controller is by default accessed at /app.php/my/controller, but will then +# be accessed at /my/controller directly. # #RewriteCond %{REQUEST_FILENAME} !-f #RewriteCond %{REQUEST_FILENAME} !-d From 45b3ab8e81a3cffae4d0ada8785620ea4209c207 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 16:18:02 -0500 Subject: [PATCH 1004/1142] [feature/controller] Move Response definition into a variable PHPBB3-10864 --- phpBB/includes/event/kernel_exception_subscriber.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/event/kernel_exception_subscriber.php b/phpBB/includes/event/kernel_exception_subscriber.php index cd6ea40c70..e2668d4560 100644 --- a/phpBB/includes/event/kernel_exception_subscriber.php +++ b/phpBB/includes/event/kernel_exception_subscriber.php @@ -67,7 +67,8 @@ class phpbb_event_kernel_exception_subscriber implements EventSubscriberInterfac page_footer(true, false, false); - $event->setResponse(new Response($this->template->assign_display('body'), 404)); + $response = new Response($this->template->assign_display('body'), 404); + $event->setResponse($response); } public static function getSubscribedEvents() From f42b36185c86dfe19974c866ce7e284263aff371 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 16:24:32 -0500 Subject: [PATCH 1005/1142] [feature/controller] Better explanation of the Options +FollowSymLinks line PHPBB3-10864 --- phpBB/.htaccess | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phpBB/.htaccess b/phpBB/.htaccess index d61ef7b893..3283b9beb8 100644 --- a/phpBB/.htaccess +++ b/phpBB/.htaccess @@ -23,8 +23,9 @@ #RewriteRule ^(.*)$ app.php [QSA,L] # -# On Windows, you must also uncomment the following line so that SymLinks -# are followed. +# If symbolic links are not already being followed, +# uncomment the line below. +# http://anothersysadmin.wordpress.com/2008/06/10/mod_rewrite-forbidden-403-with-apache-228/ # #Options +FollowSymLinks From b2598662af0b3a37b228cee03cbbf7d7160c7a11 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 16:26:00 -0500 Subject: [PATCH 1006/1142] [feature/controller] Clarify working paths after enabling rewriting PHPBB3-10864 --- phpBB/.htaccess | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/.htaccess b/phpBB/.htaccess index 3283b9beb8..d0f65e345c 100644 --- a/phpBB/.htaccess +++ b/phpBB/.htaccess @@ -16,7 +16,7 @@ # Uncomment the following 3 lines if you want to rewrite URLs passed through # the front controller to not use app.php in the actual URL. In other words, # a controller is by default accessed at /app.php/my/controller, but will then -# be accessed at /my/controller directly. +# be accessible at either /app.php/my/controller or just /my/controller # #RewriteCond %{REQUEST_FILENAME} !-f #RewriteCond %{REQUEST_FILENAME} !-d From a87a5dd566bd4b9481cdca86fa6f4bc3363d58de Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 15 Nov 2012 16:31:32 -0500 Subject: [PATCH 1007/1142] [feature/controller] Fix tests PHPBB3-10864 --- tests/controller/controller_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/controller/controller_test.php b/tests/controller/controller_test.php index b73019a6a0..577feee517 100644 --- a/tests/controller/controller_test.php +++ b/tests/controller/controller_test.php @@ -33,7 +33,7 @@ class phpbb_controller_test extends phpbb_test_case { $provider = new phpbb_controller_provider; $routes = $provider - ->get_paths($this->extension_manager->get_finder()) + ->import_paths_from_finder($this->extension_manager->get_finder()) ->find('./tests/controller/'); // This will need to be updated if any new routes are defined From c9588572c9f40223de3445a211ca85b18f5294a4 Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 09:20:15 -0500 Subject: [PATCH 1008/1142] [feature/controller] Add period before $phpEx PHPBB3-10864 --- phpBB/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/app.php b/phpBB/app.php index a7d645cb09..f1023ff1b5 100644 --- a/phpBB/app.php +++ b/phpBB/app.php @@ -17,7 +17,7 @@ define('IN_PHPBB', true); $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; $phpEx = substr(strrchr(__FILE__, '.'), 1); include($phpbb_root_path . 'common.' . $phpEx); -include($phpbb_root_path . 'includes/functions_url_matcher' . $phpEx); +include($phpbb_root_path . 'includes/functions_url_matcher.' . $phpEx); // Start session management $user->session_begin(); From 76917558832b0102716448770f365585aac224e9 Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 09:23:57 -0500 Subject: [PATCH 1009/1142] [feature/controller] Adapt functional tests given new controller framework PHPBB3-10864 --- .../functional/extension_controller_test.php | 129 +++++++----------- .../fixtures/ext/error/class/controller.php | 14 -- .../fixtures/ext/error/class/ext.php | 6 - .../ext/error/classtype/controller.php | 15 -- .../fixtures/ext/error/classtype/ext.php | 6 - .../ext/error/disabled/controller.php | 14 -- .../fixtures/ext/error/disabled/ext.php | 6 - .../fixtures/ext/foo/bar/config/routing.yml | 3 + .../fixtures/ext/foo/bar/config/services.yml | 3 + .../fixtures/ext/foo/bar/controller.php | 14 -- .../ext/foo/bar/controller/controller.php | 10 ++ tests/functional/fixtures/ext/foo/bar/ext.php | 12 +- .../prosilver/template/foobar_body.html | 5 - .../fixtures/ext/foobar/controller.php | 14 -- tests/functional/fixtures/ext/foobar/ext.php | 6 - .../prosilver/template/foobar_body.html | 5 - 16 files changed, 69 insertions(+), 193 deletions(-) delete mode 100644 tests/functional/fixtures/ext/error/class/controller.php delete mode 100644 tests/functional/fixtures/ext/error/class/ext.php delete mode 100644 tests/functional/fixtures/ext/error/classtype/controller.php delete mode 100644 tests/functional/fixtures/ext/error/classtype/ext.php delete mode 100644 tests/functional/fixtures/ext/error/disabled/controller.php delete mode 100644 tests/functional/fixtures/ext/error/disabled/ext.php create mode 100755 tests/functional/fixtures/ext/foo/bar/config/routing.yml create mode 100755 tests/functional/fixtures/ext/foo/bar/config/services.yml delete mode 100644 tests/functional/fixtures/ext/foo/bar/controller.php create mode 100755 tests/functional/fixtures/ext/foo/bar/controller/controller.php mode change 100644 => 100755 tests/functional/fixtures/ext/foo/bar/ext.php delete mode 100644 tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foobar_body.html delete mode 100644 tests/functional/fixtures/ext/foobar/controller.php delete mode 100644 tests/functional/fixtures/ext/foobar/ext.php delete mode 100644 tests/functional/fixtures/ext/foobar/styles/prosilver/template/foobar_body.html diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index d92a830365..2f07c8a70f 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -13,6 +13,14 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_case { protected $phpbb_extension_manager; + + static protected $fixtures = array( + 'foo/bar/config/routing.yml', + 'foo/bar/config/services.yml', + 'foo/bar/controller/controller.php', + 'foo/bar/ext.php', + ); + /** * This should only be called once before the tests are run. * This is used to copy the fixtures to the phpBB install @@ -22,15 +30,10 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c global $phpbb_root_path; parent::setUpBeforeClass(); - // these directories need to be created before the files can be copied $directories = array( - $phpbb_root_path . 'ext/error/class/', - $phpbb_root_path . 'ext/error/classtype/', - $phpbb_root_path . 'ext/error/disabled/', $phpbb_root_path . 'ext/foo/bar/', - $phpbb_root_path . 'ext/foo/bar/styles/prosilver/template/', - $phpbb_root_path . 'ext/foobar/', - $phpbb_root_path . 'ext/foobar/styles/prosilver/template/', + $phpbb_root_path . 'ext/foo/bar/config/', + $phpbb_root_path . 'ext/foo/bar/controller/', ); foreach ($directories as $dir) @@ -40,23 +43,8 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c mkdir($dir, 0777, true); } } - - $fixtures = array( - 'error/class/controller.php', - 'error/class/ext.php', - 'error/classtype/controller.php', - 'error/classtype/ext.php', - 'error/disabled/controller.php', - 'error/disabled/ext.php', - 'foo/bar/controller.php', - 'foo/bar/ext.php', - 'foo/bar/styles/prosilver/template/foobar_body.html', - 'foobar/controller.php', - 'foobar/ext.php', - 'foobar/styles/prosilver/template/foobar_body.html', - ); - - foreach ($fixtures as $fixture) + + foreach (self::$fixtures as $fixture) { if (!copy("tests/functional/fixtures/ext/$fixture", "{$phpbb_root_path}ext/$fixture")) { @@ -65,6 +53,27 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c } } + /** + * This should only be called once after the tests are run. + * This is used to remove the fixtures from the phpBB install + */ + static public function tearDownAfterClass() + { + global $phpbb_root_path; + foreach (self::$fixtures as $fixture) + { + if (!unlink("{$phpbb_root_path}ext/$fixture")) + { + echo 'Could not delete file ' . $fixture; + } + } + + rmdir("{$phpbb_root_path}ext/foo/bar/config"); + rmdir("{$phpbb_root_path}ext/foo/bar/controller"); + rmdir("{$phpbb_root_path}ext/foo/bar"); + rmdir("{$phpbb_root_path}ext/foo"); + } + public function setUp() { parent::setUp(); @@ -75,72 +84,28 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c } /** - * Check an extension at ./ext/foobar/ which should have the class - * phpbb_ext_foobar_controller - */ - public function test_foobar() - { - $this->phpbb_extension_manager->enable('foobar'); - $crawler = $this->request('GET', 'index.php?ext=foobar'); - $this->assert_response_success(); - $this->assertContains("This is for testing purposes.", $crawler->filter('#page-body')->text()); - $this->phpbb_extension_manager->purge('foobar'); - } - - /** - * Check an extension at ./ext/foo/bar/ which should have the class - * phpbb_ext_foo_bar_controller + * Check a controller for extension foo/bar */ public function test_foo_bar() { $this->phpbb_extension_manager->enable('foo/bar'); - $crawler = $this->request('GET', 'index.php?ext=foo/bar'); - $this->assert_response_success(); - $this->assertContains("This is for testing purposes.", $crawler->filter('#page-body')->text()); - $this->phpbb_extension_manager->purge('foo/bar'); + $crawler = $this->request('GET', 'app.php/foo/bar'); + $this->assertContains("foo/bar controller handle() method", $crawler->filter('body')->text()); + $this->phpbb_extension_manager->purge('foobar'); } /** - * Check the error produced by extension at ./ext/error/class which has class - * phpbb_ext_foobar_controller + * Check the error produced by extension at ./ext/does/not/exist + * + * If an extension is disabled, its routes are not loaded. Because we + * are not looking for a controller based on a specified extension, + * we don't know the difference between a route in a disabled + * extension and a route that is not defined anyway; it is the same + * error message. */ - public function test_error_class_name() + public function test_error_ext_disabled_or_404() { - $this->phpbb_extension_manager->enable('error/class'); - $crawler = $this->request('GET', 'index.php?ext=error/class'); - $this->assertContains("The extension error/class is missing a controller class and cannot be accessed through the front-end.", $crawler->filter('#message')->text()); - $this->phpbb_extension_manager->purge('error/class'); - } - - /** - * Check the error produced by extension at ./ext/error/classtype which has class - * phpbb_ext_error_classtype_controller but does not implement phpbb_extension_controller_interface - */ - public function test_error_class_type() - { - $this->phpbb_extension_manager->enable('error/classtype'); - $crawler = $this->request('GET', 'index.php?ext=error/classtype'); - $this->assertContains("The extension controller class phpbb_ext_error_classtype_controller is not an instance of the phpbb_extension_controller_interface.", $crawler->filter('#message')->text()); - $this->phpbb_extension_manager->purge('error/classtype'); - } - - /** - * Check the error produced by extension at ./ext/error/disabled that is (obviously) - * a disabled extension - */ - public function test_error_ext_disabled() - { - $crawler = $this->request('GET', 'index.php?ext=error/disabled'); - $this->assertContains("The extension error/disabled is not enabled", $crawler->filter('#message')->text()); - } - - /** - * Check the error produced by extension at ./ext/error/404 that is (obviously) - * not existant - */ - public function test_error_ext_missing() - { - $crawler = $this->request('GET', 'index.php?ext=error/404'); - $this->assertContains("The extension error/404 does not exist.", $crawler->filter('#message')->text()); + $crawler = $this->request('GET', 'app.php/does/not/exist'); + $this->assertContains('No route found for "GET /does/not/exist"', $crawler->filter('body')->text()); } } diff --git a/tests/functional/fixtures/ext/error/class/controller.php b/tests/functional/fixtures/ext/error/class/controller.php deleted file mode 100644 index 74bbbee540..0000000000 --- a/tests/functional/fixtures/ext/error/class/controller.php +++ /dev/null @@ -1,14 +0,0 @@ -template->set_filenames(array( - 'body' => 'index_body.html' - )); - - page_header('Test extension'); - page_footer(); - } -} diff --git a/tests/functional/fixtures/ext/error/class/ext.php b/tests/functional/fixtures/ext/error/class/ext.php deleted file mode 100644 index f97ad2b838..0000000000 --- a/tests/functional/fixtures/ext/error/class/ext.php +++ /dev/null @@ -1,6 +0,0 @@ -set_filenames(array( - 'body' => 'index_body.html' - )); - - page_header('Test extension'); - page_footer(); - } -} diff --git a/tests/functional/fixtures/ext/error/classtype/ext.php b/tests/functional/fixtures/ext/error/classtype/ext.php deleted file mode 100644 index 35b1cd15a2..0000000000 --- a/tests/functional/fixtures/ext/error/classtype/ext.php +++ /dev/null @@ -1,6 +0,0 @@ -template->set_filenames(array( - 'body' => 'index_body.html' - )); - - page_header('Test extension'); - page_footer(); - } -} diff --git a/tests/functional/fixtures/ext/error/disabled/ext.php b/tests/functional/fixtures/ext/error/disabled/ext.php deleted file mode 100644 index aec8051848..0000000000 --- a/tests/functional/fixtures/ext/error/disabled/ext.php +++ /dev/null @@ -1,6 +0,0 @@ -template->set_filenames(array( - 'body' => 'foobar_body.html' - )); - - page_header('Test extension'); - page_footer(); - } -} diff --git a/tests/functional/fixtures/ext/foo/bar/controller/controller.php b/tests/functional/fixtures/ext/foo/bar/controller/controller.php new file mode 100755 index 0000000000..4c5274951f --- /dev/null +++ b/tests/functional/fixtures/ext/foo/bar/controller/controller.php @@ -0,0 +1,10 @@ + - -
This is for testing purposes.
- - diff --git a/tests/functional/fixtures/ext/foobar/controller.php b/tests/functional/fixtures/ext/foobar/controller.php deleted file mode 100644 index ff35f12ee0..0000000000 --- a/tests/functional/fixtures/ext/foobar/controller.php +++ /dev/null @@ -1,14 +0,0 @@ -template->set_filenames(array( - 'body' => 'foobar_body.html' - )); - - page_header('Test extension'); - page_footer(); - } -} diff --git a/tests/functional/fixtures/ext/foobar/ext.php b/tests/functional/fixtures/ext/foobar/ext.php deleted file mode 100644 index 7cf443d369..0000000000 --- a/tests/functional/fixtures/ext/foobar/ext.php +++ /dev/null @@ -1,6 +0,0 @@ - - -
This is for testing purposes.
- - From 74bc46049303952e5f16f61e9f63fa2eeaffbadc Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 09:43:08 -0500 Subject: [PATCH 1010/1142] [feature/controller] Rename improperly named controller exception class PHPBB3-10864 --- phpBB/includes/controller/exception.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/controller/exception.php b/phpBB/includes/controller/exception.php index da2fefc600..faa8b6b584 100644 --- a/phpBB/includes/controller/exception.php +++ b/phpBB/includes/controller/exception.php @@ -19,6 +19,6 @@ if (!defined('IN_PHPBB')) * Controller exception class * @package phpBB3 */ -class phpbb_controller_provider extends RuntimeException +class phpbb_controller_exception extends RuntimeException { } From 120267e58030eb4b7184e0d54166dfc8d905c3f7 Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 10:27:55 -0500 Subject: [PATCH 1011/1142] [feature/controller] Correctly access user object PHPBB3-10864 --- phpBB/includes/controller/resolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/controller/resolver.php b/phpBB/includes/controller/resolver.php index 0e724ec608..5674a65118 100644 --- a/phpBB/includes/controller/resolver.php +++ b/phpBB/includes/controller/resolver.php @@ -115,7 +115,7 @@ class phpbb_controller_resolver implements ControllerResolverInterface } else { - throw new phpbb_controller_exception($user->lang('CONTROLLER_ARGUMENT_VALUE_MISSING', $param->getPosition() + 1, get_class($object) . ':' . $method, $param->name)); + throw new phpbb_controller_exception($this->user->lang('CONTROLLER_ARGUMENT_VALUE_MISSING', $param->getPosition() + 1, get_class($object) . ':' . $method, $param->name)); } } From e516680859744e69b696ac0054ec3aefd94175b7 Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 10:28:35 -0500 Subject: [PATCH 1012/1142] [feature/controller] Use sizeof() instead of count() as per guidelines PHPBB3-10864 --- tests/controller/controller_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/controller/controller_test.php b/tests/controller/controller_test.php index 577feee517..5a4c65e109 100644 --- a/tests/controller/controller_test.php +++ b/tests/controller/controller_test.php @@ -37,7 +37,7 @@ class phpbb_controller_test extends phpbb_test_case ->find('./tests/controller/'); // This will need to be updated if any new routes are defined - $this->assertEquals(2, count($routes)); + $this->assertEquals(2, sizeof($routes)); } public function test_controller_resolver() From 230897723c9e5fc5534d9781b04ffd8dac70d51b Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 10:29:35 -0500 Subject: [PATCH 1013/1142] [feature/controller] Reword comment for clarification PHPBB3-10864 --- tests/controller/controller_test.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/controller/controller_test.php b/tests/controller/controller_test.php index 5a4c65e109..198fb3c6dd 100644 --- a/tests/controller/controller_test.php +++ b/tests/controller/controller_test.php @@ -43,9 +43,8 @@ class phpbb_controller_test extends phpbb_test_case public function test_controller_resolver() { $container = new ContainerBuilder(); - // For some reason, I cannot get it to load more than one services - // file at a time, even when givein multiple paths - // So instead, I am looping through all of the paths + // YamlFileLoader only uses one path at a time, so we need to loop + // through all of the ones we are using. foreach (array(__DIR__.'/config', __DIR__.'/ext/foo/config') as $path) { $loader = new YamlFileLoader($container, new FileLocator($path)); @@ -53,7 +52,7 @@ class phpbb_controller_test extends phpbb_test_case } // Autoloading classes within the tests folder does not work - // so I'll include them manually + // so I'll include them manually. if (!class_exists('phpbb_ext_foo_controller')) { include(__DIR__.'/ext/foo/controller.php'); From 0c75d3d7da9c5fd7ee1560597db684c9d1704c22 Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 10:30:50 -0500 Subject: [PATCH 1014/1142] [feature/controller] Add test for missing argument in controller class PHPBB3-10864 --- tests/functional/extension_controller_test.php | 10 +++++++++- .../functional/fixtures/ext/foo/bar/config/routing.yml | 4 ++++ .../fixtures/ext/foo/bar/controller/controller.php | 5 +++++ tests/functional/fixtures/ext/foo/bar/ext.php | 2 +- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index 2f07c8a70f..b1e910aeac 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -91,7 +91,15 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c $this->phpbb_extension_manager->enable('foo/bar'); $crawler = $this->request('GET', 'app.php/foo/bar'); $this->assertContains("foo/bar controller handle() method", $crawler->filter('body')->text()); - $this->phpbb_extension_manager->purge('foobar'); + $this->phpbb_extension_manager->purge('foo/bar'); + } + + public function test_missing_argument() + { + $this->phpbb_extension_manager->enable('foo/bar'); + $crawler = $this->request('GET', 'app.php/foo/baz'); + $this->assertContains('Missing value for argument #1: test in class phpbb_ext_foo_bar_controller:baz', $crawler->filter('body')->text()); + $this->phpbb_extension_manager->purge('foo/bar'); } /** diff --git a/tests/functional/fixtures/ext/foo/bar/config/routing.yml b/tests/functional/fixtures/ext/foo/bar/config/routing.yml index 7ecaba9e71..945ef51366 100755 --- a/tests/functional/fixtures/ext/foo/bar/config/routing.yml +++ b/tests/functional/fixtures/ext/foo/bar/config/routing.yml @@ -1,3 +1,7 @@ foo_bar_controller: pattern: /foo/bar defaults: { _controller: foo_bar.controller:handle } + +foo_baz_controller: + pattern: /foo/baz + defaults: { _controller: foo_bar.controller:baz } diff --git a/tests/functional/fixtures/ext/foo/bar/controller/controller.php b/tests/functional/fixtures/ext/foo/bar/controller/controller.php index 4c5274951f..def5184e8c 100755 --- a/tests/functional/fixtures/ext/foo/bar/controller/controller.php +++ b/tests/functional/fixtures/ext/foo/bar/controller/controller.php @@ -7,4 +7,9 @@ class phpbb_ext_foo_bar_controller { return new Response('foo/bar controller handle() method', 200); } + + public function baz($test) + { + return new Response('Value of "test" URL argument is: ' . $test); + } } diff --git a/tests/functional/fixtures/ext/foo/bar/ext.php b/tests/functional/fixtures/ext/foo/bar/ext.php index b50f95990e..7170209d53 100755 --- a/tests/functional/fixtures/ext/foo/bar/ext.php +++ b/tests/functional/fixtures/ext/foo/bar/ext.php @@ -1,6 +1,6 @@ Date: Fri, 16 Nov 2012 10:35:55 -0500 Subject: [PATCH 1015/1142] [feature/controller] Fix param block for controller callable PHPBB3-10864 --- phpBB/includes/controller/resolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/controller/resolver.php b/phpBB/includes/controller/resolver.php index 5674a65118..901aa7eaa0 100644 --- a/phpBB/includes/controller/resolver.php +++ b/phpBB/includes/controller/resolver.php @@ -90,7 +90,7 @@ class phpbb_controller_resolver implements ControllerResolverInterface * controller. * * @param Symfony\Component\HttpFoundation\Request $request Symfony Request object - * @param string $controller Controller class name + * @param mixed $controller A callable (controller class, method) * @return bool False * @throws phpbb_controller_exception */ From d41b1146e8947919ed9bdd41e6a30ad15e91d262 Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 10:37:07 -0500 Subject: [PATCH 1016/1142] [feature/controller] Rename $root_path class property to $phpbb_root_path PHPBB3-10864 --- phpBB/includes/controller/helper.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/controller/helper.php b/phpBB/includes/controller/helper.php index 6b697c7e46..6dc3ec4626 100644 --- a/phpBB/includes/controller/helper.php +++ b/phpBB/includes/controller/helper.php @@ -39,7 +39,7 @@ class phpbb_controller_helper * phpBB root path * @var string */ - protected $root_path; + protected $phpbb_root_path; /** * PHP extension @@ -52,14 +52,14 @@ class phpbb_controller_helper * * @param phpbb_template $template Template object * @param phpbb_user $user User object - * @param string $root_path phpBB root path + * @param string $phpbb_root_path phpBB root path * @param string $php_ext PHP extension */ - public function __construct(phpbb_template $template, phpbb_user $user, $root_path, $php_ext) + public function __construct(phpbb_template $template, phpbb_user $user, $phpbb_root_path, $php_ext) { $this->template = $template; $this->user = $user; - $this->root_path = $root_path; + $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $php_ext; } @@ -95,7 +95,7 @@ class phpbb_controller_helper */ public function url(array $url_parts, $query = '') { - return append_sid($this->root_path . implode('/', $url_parts), $query); + return append_sid($this->phpbb_root_path . implode('/', $url_parts), $query); } /** From 1952a4f276930edd16ca51fd690ed3e7965071da Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 10:38:40 -0500 Subject: [PATCH 1017/1142] [feature/controller] Flip method parameters, require $message PHPBB3-10864 --- phpBB/includes/controller/helper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/controller/helper.php b/phpBB/includes/controller/helper.php index 6dc3ec4626..6cacc8fefa 100644 --- a/phpBB/includes/controller/helper.php +++ b/phpBB/includes/controller/helper.php @@ -101,11 +101,11 @@ class phpbb_controller_helper /** * Output an error, effectively the same thing as trigger_error * - * @param string $code The error code (e.g. 404, 500, 503, etc.) * @param string $message The error message + * @param string $code The error code (e.g. 404, 500, 503, etc.) * @return Response A Reponse instance */ - public function error($code = 500, $message = '') + public function error($message, $code = 500) { $this->template->assign_vars(array( 'MESSAGE_TEXT' => $message, From ba1acdca0364a9cd54c705d946fbae2144c59554 Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 10:42:10 -0500 Subject: [PATCH 1018/1142] [feature/controller] Use warning instead of echo for copy() and unlink() PHPBB3-10864 --- tests/functional/extension_controller_test.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index b1e910aeac..65a9c80df6 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -46,10 +46,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c foreach (self::$fixtures as $fixture) { - if (!copy("tests/functional/fixtures/ext/$fixture", "{$phpbb_root_path}ext/$fixture")) - { - echo 'Could not copy file ' . $fixture; - } + copy("tests/functional/fixtures/ext/$fixture", "{$phpbb_root_path}ext/$fixture"); } } @@ -62,10 +59,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c global $phpbb_root_path; foreach (self::$fixtures as $fixture) { - if (!unlink("{$phpbb_root_path}ext/$fixture")) - { - echo 'Could not delete file ' . $fixture; - } + unlink("{$phpbb_root_path}ext/$fixture"); } rmdir("{$phpbb_root_path}ext/foo/bar/config"); From 5b013ddf5c48e71166dcefd6d384aea1d801698a Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 11:41:05 -0500 Subject: [PATCH 1019/1142] [feature/controller] Add controller functional test with template PHPBB3-10864 --- .../functional/extension_controller_test.php | 32 ++++++++++++++++--- .../fixtures/ext/foo/bar/config/routing.yml | 4 +++ .../fixtures/ext/foo/bar/config/services.yml | 3 ++ .../ext/foo/bar/controller/controller.php | 15 +++++++++ .../prosilver/template/foo_bar_body.html | 3 ++ 5 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foo_bar_body.html diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index 65a9c80df6..9cc2e0e32f 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -18,7 +18,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c 'foo/bar/config/routing.yml', 'foo/bar/config/services.yml', 'foo/bar/controller/controller.php', - 'foo/bar/ext.php', + 'foo/bar/styles/prosilver/template/foo_bar_body.html', ); /** @@ -34,6 +34,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c $phpbb_root_path . 'ext/foo/bar/', $phpbb_root_path . 'ext/foo/bar/config/', $phpbb_root_path . 'ext/foo/bar/controller/', + $phpbb_root_path . 'ext/foo/bar/styles/prosilver/template', ); foreach ($directories as $dir) @@ -43,10 +44,12 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c mkdir($dir, 0777, true); } } - + foreach (self::$fixtures as $fixture) { - copy("tests/functional/fixtures/ext/$fixture", "{$phpbb_root_path}ext/$fixture"); + copy( + "tests/functional/fixtures/ext/$fixture", + "{$phpbb_root_path}ext/$fixture"); } } @@ -57,6 +60,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c static public function tearDownAfterClass() { global $phpbb_root_path; + foreach (self::$fixtures as $fixture) { unlink("{$phpbb_root_path}ext/$fixture"); @@ -64,6 +68,9 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c rmdir("{$phpbb_root_path}ext/foo/bar/config"); rmdir("{$phpbb_root_path}ext/foo/bar/controller"); + rmdir("{$phpbb_root_path}ext/foo/bar/styles/prosilver/template"); + rmdir("{$phpbb_root_path}ext/foo/bar/styles/prosilver"); + rmdir("{$phpbb_root_path}ext/foo/bar/styles"); rmdir("{$phpbb_root_path}ext/foo/bar"); rmdir("{$phpbb_root_path}ext/foo"); } @@ -78,7 +85,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c } /** - * Check a controller for extension foo/bar + * Check a controller for extension foo/bar. */ public function test_foo_bar() { @@ -88,6 +95,21 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c $this->phpbb_extension_manager->purge('foo/bar'); } + /** + * Check the output of a controller using the template system + */ + public function test_controller_with_template() + { + $this->phpbb_extension_manager->enable('foo/bar'); + $crawler = $this->request('GET', 'app.php/foo/template'); + $this->assertContains("I am a variable", $crawler->filter('#content')->text()); + $this->phpbb_extension_manager->purge('foo/bar'); + } + + /** + * Check the error produced by calling a controller without a required + * argument. + */ public function test_missing_argument() { $this->phpbb_extension_manager->enable('foo/bar'); @@ -97,7 +119,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c } /** - * Check the error produced by extension at ./ext/does/not/exist + * Check the error produced by extension at ./ext/does/not/exist. * * If an extension is disabled, its routes are not loaded. Because we * are not looking for a controller based on a specified extension, diff --git a/tests/functional/fixtures/ext/foo/bar/config/routing.yml b/tests/functional/fixtures/ext/foo/bar/config/routing.yml index 945ef51366..123f5f1b73 100755 --- a/tests/functional/fixtures/ext/foo/bar/config/routing.yml +++ b/tests/functional/fixtures/ext/foo/bar/config/routing.yml @@ -5,3 +5,7 @@ foo_bar_controller: foo_baz_controller: pattern: /foo/baz defaults: { _controller: foo_bar.controller:baz } + +foo_template_controller: + pattern: /foo/template + defaults: { _controller: foo_bar.controller:template } diff --git a/tests/functional/fixtures/ext/foo/bar/config/services.yml b/tests/functional/fixtures/ext/foo/bar/config/services.yml index d1db6fcb1d..33ced55af9 100755 --- a/tests/functional/fixtures/ext/foo/bar/config/services.yml +++ b/tests/functional/fixtures/ext/foo/bar/config/services.yml @@ -1,3 +1,6 @@ services: foo_bar.controller: class: phpbb_ext_foo_bar_controller + arguments: + - @controller.helper + - @template diff --git a/tests/functional/fixtures/ext/foo/bar/controller/controller.php b/tests/functional/fixtures/ext/foo/bar/controller/controller.php index def5184e8c..50ea5d034b 100755 --- a/tests/functional/fixtures/ext/foo/bar/controller/controller.php +++ b/tests/functional/fixtures/ext/foo/bar/controller/controller.php @@ -3,6 +3,14 @@ use Symfony\Component\HttpFoundation\Response; class phpbb_ext_foo_bar_controller { + protected $template; + + public function __construct(phpbb_controller_helper $helper, phpbb_template $template) + { + $this->template = $template; + $this->helper = $helper; + } + public function handle() { return new Response('foo/bar controller handle() method', 200); @@ -12,4 +20,11 @@ class phpbb_ext_foo_bar_controller { return new Response('Value of "test" URL argument is: ' . $test); } + + public function template() + { + $this->template->assign_var('A_VARIABLE', 'I am a variable'); + + return $this->helper->render('foo_bar_body.html'); + } } diff --git a/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foo_bar_body.html b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foo_bar_body.html new file mode 100644 index 0000000000..8fb6994d3d --- /dev/null +++ b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foo_bar_body.html @@ -0,0 +1,3 @@ + +
{A_VARIABLE}
+ From abf2575bdbad84ca2d139290789852ee51efd31c Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 13:04:12 -0500 Subject: [PATCH 1020/1142] [feature/controller] Remove URL rewriting by default PHPBB3-10864 --- phpBB/web.config | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/phpBB/web.config b/phpBB/web.config index e31a48b991..a73c328626 100644 --- a/phpBB/web.config +++ b/phpBB/web.config @@ -12,18 +12,6 @@ - - - - - - - - - - - - From 4efbb893b7b8ada8766847dc59724faef9c18142 Mon Sep 17 00:00:00 2001 From: David King Date: Fri, 16 Nov 2012 17:36:39 -0500 Subject: [PATCH 1021/1142] [feature/controller] Fix line endings and permissions, and check responses PHPBB3-10864 --- tests/functional/extension_controller_test.php | 4 ++++ .../fixtures/ext/foo/bar/config/routing.yml | 0 .../fixtures/ext/foo/bar/config/services.yml | 0 .../fixtures/ext/foo/bar/controller/controller.php | 0 tests/functional/fixtures/ext/foo/bar/ext.php | 12 ++++++------ 5 files changed, 10 insertions(+), 6 deletions(-) mode change 100755 => 100644 tests/functional/fixtures/ext/foo/bar/config/routing.yml mode change 100755 => 100644 tests/functional/fixtures/ext/foo/bar/config/services.yml mode change 100755 => 100644 tests/functional/fixtures/ext/foo/bar/controller/controller.php mode change 100755 => 100644 tests/functional/fixtures/ext/foo/bar/ext.php diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index 9cc2e0e32f..ba4a4e8ef0 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -91,6 +91,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c { $this->phpbb_extension_manager->enable('foo/bar'); $crawler = $this->request('GET', 'app.php/foo/bar'); + $this->assert_response_success(); $this->assertContains("foo/bar controller handle() method", $crawler->filter('body')->text()); $this->phpbb_extension_manager->purge('foo/bar'); } @@ -102,6 +103,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c { $this->phpbb_extension_manager->enable('foo/bar'); $crawler = $this->request('GET', 'app.php/foo/template'); + $this->assert_response_success(); $this->assertContains("I am a variable", $crawler->filter('#content')->text()); $this->phpbb_extension_manager->purge('foo/bar'); } @@ -114,6 +116,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c { $this->phpbb_extension_manager->enable('foo/bar'); $crawler = $this->request('GET', 'app.php/foo/baz'); + $this->assertEquals(404, $this->client->getResponse()->getStatus()); $this->assertContains('Missing value for argument #1: test in class phpbb_ext_foo_bar_controller:baz', $crawler->filter('body')->text()); $this->phpbb_extension_manager->purge('foo/bar'); } @@ -130,6 +133,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c public function test_error_ext_disabled_or_404() { $crawler = $this->request('GET', 'app.php/does/not/exist'); + $this->assertEquals(404, $this->client->getResponse()->getStatus()); $this->assertContains('No route found for "GET /does/not/exist"', $crawler->filter('body')->text()); } } diff --git a/tests/functional/fixtures/ext/foo/bar/config/routing.yml b/tests/functional/fixtures/ext/foo/bar/config/routing.yml old mode 100755 new mode 100644 diff --git a/tests/functional/fixtures/ext/foo/bar/config/services.yml b/tests/functional/fixtures/ext/foo/bar/config/services.yml old mode 100755 new mode 100644 diff --git a/tests/functional/fixtures/ext/foo/bar/controller/controller.php b/tests/functional/fixtures/ext/foo/bar/controller/controller.php old mode 100755 new mode 100644 diff --git a/tests/functional/fixtures/ext/foo/bar/ext.php b/tests/functional/fixtures/ext/foo/bar/ext.php old mode 100755 new mode 100644 index 7170209d53..74359d51ab --- a/tests/functional/fixtures/ext/foo/bar/ext.php +++ b/tests/functional/fixtures/ext/foo/bar/ext.php @@ -1,6 +1,6 @@ - Date: Sat, 17 Nov 2012 01:15:50 +0100 Subject: [PATCH 1022/1142] [ticket/11212] Do not rely on $request in send_status_line() PHPBB3-11212 --- phpBB/includes/functions.php | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 3a5b100515..dd82c9dc46 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2858,8 +2858,6 @@ function meta_refresh($time, $url, $disable_cd_check = false) */ function send_status_line($code, $message) { - global $request; - if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi') { // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though @@ -2867,18 +2865,35 @@ function send_status_line($code, $message) } else { - if ($request->server('SERVER_PROTOCOL')) - { - $version = $request->server('SERVER_PROTOCOL'); - } - else - { - $version = 'HTTP/1.0'; - } + $version = get_http_version(); header("$version $code $message", true, $code); } } +/** +* Returns the HTTP version used in the current request. +* +* Handles the case of being called before `$request` is present, +* In which case it falls back to the $_SERVER superglobal. +* +* @return string HTTP version +*/ +function get_http_version() +{ + global $request; + + if ($request && $request->server('SERVER_PROTOCOL')) + { + return $request->server('SERVER_PROTOCOL'); + } + else if (isset($_SERVER['SERVER_PROTOCOL'])) + { + return $_SERVER['SERVER_PROTOCOL']; + } + + return 'HTTP/1.0'; +} + //Form validation From 9cdef7984f5162fa19ce36852331f79de3561f66 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 17 Nov 2012 01:17:23 +0100 Subject: [PATCH 1023/1142] [ticket/11212] Allow dispatcher to be absent during garbage_collection() PHPBB3-11212 --- phpBB/includes/functions.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index dd82c9dc46..4754d5194f 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5348,7 +5348,9 @@ function garbage_collection() * @event core.garbage_collection * @since 3.1-A1 */ - $phpbb_dispatcher->dispatch('core.garbage_collection'); + if (!empty($phpbb_dispatcher)) { + $phpbb_dispatcher->dispatch('core.garbage_collection'); + } // Unload cache, must be done before the DB connection if closed if (!empty($cache)) From 98921e0b87b25fcd5985edb747d5768cd8ccf18e Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 17 Nov 2012 01:17:45 +0100 Subject: [PATCH 1024/1142] [ticket/11213] Add missing global in install_update.php PHPBB3-11213 --- phpBB/install/install_update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 8c044550f3..7f40015002 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -71,7 +71,7 @@ class install_update extends module function main($mode, $sub) { - global $style, $template, $phpEx, $phpbb_root_path, $user, $db, $config, $cache, $auth, $language; + global $phpbb_style, $template, $phpEx, $phpbb_root_path, $user, $db, $config, $cache, $auth, $language; global $request; $this->tpl_name = 'install_update'; From b534a7a5790df55ab5f0d8aba8f40080d481bac4 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 17 Nov 2012 01:25:14 +0100 Subject: [PATCH 1025/1142] [ticket/11212] Rename get_http_version to phpbb_request_http_version() PHPBB3-11212 --- phpBB/includes/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 4754d5194f..495f83e3a6 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2865,7 +2865,7 @@ function send_status_line($code, $message) } else { - $version = get_http_version(); + $version = phpbb_request_http_version(); header("$version $code $message", true, $code); } } @@ -2878,7 +2878,7 @@ function send_status_line($code, $message) * * @return string HTTP version */ -function get_http_version() +function phpbb_request_http_version() { global $request; From 1affc35be9a5ee2cdf2cd2551e708c928fb96d88 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 17 Nov 2012 01:25:38 +0100 Subject: [PATCH 1026/1142] [ticket/11212] Cosmetics PHPBB3-11212 --- phpBB/includes/functions.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 495f83e3a6..dbc040e5fe 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2873,7 +2873,7 @@ function send_status_line($code, $message) /** * Returns the HTTP version used in the current request. * -* Handles the case of being called before `$request` is present, +* Handles the case of being called before $request is present, * In which case it falls back to the $_SERVER superglobal. * * @return string HTTP version @@ -5348,7 +5348,8 @@ function garbage_collection() * @event core.garbage_collection * @since 3.1-A1 */ - if (!empty($phpbb_dispatcher)) { + if (!empty($phpbb_dispatcher)) + { $phpbb_dispatcher->dispatch('core.garbage_collection'); } From b8cf74217aacb90ac066eee4e8812a2c32caa58a Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 17 Nov 2012 01:32:40 +0100 Subject: [PATCH 1027/1142] [ticket/11212] Cosmetic surgery done right PHPBB3-11212 --- phpBB/includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index dbc040e5fe..ab4c7e1772 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2874,7 +2874,7 @@ function send_status_line($code, $message) * Returns the HTTP version used in the current request. * * Handles the case of being called before $request is present, -* In which case it falls back to the $_SERVER superglobal. +* in which case it falls back to the $_SERVER superglobal. * * @return string HTTP version */ From 8913b2c7c4ffc38d4caf34ca7014b8a07f11d19d Mon Sep 17 00:00:00 2001 From: David King Date: Sat, 17 Nov 2012 17:48:20 -0500 Subject: [PATCH 1028/1142] [feature/controller] Use query string, not path info, for controller access This is hopefully just temporary until we can fix the relative path issue. PHPBB3-10864 --- phpBB/app.php | 8 ++++++++ phpBB/common.php | 7 ------- phpBB/includes/functions.php | 16 +--------------- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/phpBB/app.php b/phpBB/app.php index f1023ff1b5..13bf3f0be1 100644 --- a/phpBB/app.php +++ b/phpBB/app.php @@ -7,6 +7,8 @@ * */ +use Symfony\Component\HttpFoundation\Request; + /** */ @@ -24,6 +26,12 @@ $user->session_begin(); $auth->acl($user->data); $user->setup('app'); +// Until we fix the issue with relative paths, we have to fake path info to +// allow urls like app.php?controller=foo/bar +$controller = $request->variable('controller', '', false, phpbb_request_interface::GET); +$uri = '/' . $controller; +$symfony_request = Request::create($uri); + $http_kernel = $phpbb_container->get('http_kernel'); $response = $http_kernel->handle($symfony_request); $response->send(); diff --git a/phpBB/common.php b/phpBB/common.php index a5ffcea8e4..e99b9edee5 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -8,8 +8,6 @@ * Minimum Requirement: PHP 5.3.3 */ -use Symfony\Component\HttpFoundation\Request; - /** */ if (!defined('IN_PHPBB')) @@ -105,11 +103,6 @@ $phpbb_class_loader_ext->set_cache($phpbb_container->get('cache.driver')); // set up caching $cache = $phpbb_container->get('cache'); -// Instantiate the Symfony Request object -// This must be done before phpbb_request -// because otherwise globals are disabled -$symfony_request = Request::createFromGlobals(); - // Instantiate some basic classes $phpbb_dispatcher = $phpbb_container->get('dispatcher'); $request = $phpbb_container->get('request'); diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 88ce142195..17fc16ef86 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2346,20 +2346,6 @@ function append_sid($url, $params = false, $is_amp = true, $session_id = false) $params = false; } - // Make sure we have a Symfony Request object; tests do not have one - // unless they need it. - if ($symfony_request) - { - // Correct the path when we are accessing it through a controller - // This simply rewrites the value given by $phpbb_root_path to the - // script_path in config. - $path_info = $symfony_request->getPathInfo(); - if (!empty($path_info) && $path_info != '/') - { - $url = $config['script_path'] . '/' . substr($url, strlen($phpbb_root_path)); - } - } - $append_sid_overwrite = false; /** @@ -5056,7 +5042,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 // Determine board url - we may need it later $board_url = generate_board_url() . '/'; - $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $config['script_path'] . '/'; + $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $phpbb_root_path; // Send a proper content-language to the output $user_lang = $user->lang['USER_LANG']; From 4d6f6351dd0563b26105d15b98052d907f9c52ed Mon Sep 17 00:00:00 2001 From: David King Date: Sat, 17 Nov 2012 18:05:32 -0500 Subject: [PATCH 1029/1142] [feature/controller] Allow injecting Symfony Request into controllers PHPBB3-10864 --- phpBB/includes/controller/resolver.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpBB/includes/controller/resolver.php b/phpBB/includes/controller/resolver.php index 901aa7eaa0..ee469aa9c8 100644 --- a/phpBB/includes/controller/resolver.php +++ b/phpBB/includes/controller/resolver.php @@ -109,6 +109,10 @@ class phpbb_controller_resolver implements ControllerResolverInterface { $arguments[] = $attributes[$param->name]; } + else if ($param->getClass() && $param->getClass()->isInstance($request)) + { + $arguments[] = $request; + } else if ($param->isDefaultValueAvailable()) { $arguments[] = $param->getDefaultValue(); From 7a3d9ed85dfd7f32a938070fff854e56bf39738e Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 18 Nov 2012 13:11:24 -0500 Subject: [PATCH 1030/1142] [feature/controller] Fix functional tests to use query string for controllers PHPBB3-10864 --- tests/functional/extension_controller_test.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index ba4a4e8ef0..482fe9dfd3 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -90,7 +90,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c public function test_foo_bar() { $this->phpbb_extension_manager->enable('foo/bar'); - $crawler = $this->request('GET', 'app.php/foo/bar'); + $crawler = $this->request('GET', 'app.php?controller=foo/bar'); $this->assert_response_success(); $this->assertContains("foo/bar controller handle() method", $crawler->filter('body')->text()); $this->phpbb_extension_manager->purge('foo/bar'); @@ -102,7 +102,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c public function test_controller_with_template() { $this->phpbb_extension_manager->enable('foo/bar'); - $crawler = $this->request('GET', 'app.php/foo/template'); + $crawler = $this->request('GET', 'app.php?controller=foo/template'); $this->assert_response_success(); $this->assertContains("I am a variable", $crawler->filter('#content')->text()); $this->phpbb_extension_manager->purge('foo/bar'); @@ -115,7 +115,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c public function test_missing_argument() { $this->phpbb_extension_manager->enable('foo/bar'); - $crawler = $this->request('GET', 'app.php/foo/baz'); + $crawler = $this->request('GET', 'app.php?controller=foo/baz'); $this->assertEquals(404, $this->client->getResponse()->getStatus()); $this->assertContains('Missing value for argument #1: test in class phpbb_ext_foo_bar_controller:baz', $crawler->filter('body')->text()); $this->phpbb_extension_manager->purge('foo/bar'); @@ -132,7 +132,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c */ public function test_error_ext_disabled_or_404() { - $crawler = $this->request('GET', 'app.php/does/not/exist'); + $crawler = $this->request('GET', 'app.php?controller=does/not/exist'); $this->assertEquals(404, $this->client->getResponse()->getStatus()); $this->assertContains('No route found for "GET /does/not/exist"', $crawler->filter('body')->text()); } From 09d7367dfc80f87b2fa36785a1e4285666c2d58a Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 18 Nov 2012 13:32:54 -0500 Subject: [PATCH 1031/1142] [feature/controller] Remove url rewriting until we use pathinfo in controllers PHPBB3-10864 --- phpBB/.htaccess | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/phpBB/.htaccess b/phpBB/.htaccess index d0f65e345c..474f9774c2 100644 --- a/phpBB/.htaccess +++ b/phpBB/.htaccess @@ -1,34 +1,12 @@ - -# -# Uncomment the following line if you will be using any of the URL -# rewriting below. -# -#RewriteEngine on - # # Uncomment the statement below if you want to make use of # HTTP authentication and it does not already work. # This could be required if you are for example using PHP via Apache CGI. # +# +#RewriteEngine on #RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] - -# -# Uncomment the following 3 lines if you want to rewrite URLs passed through -# the front controller to not use app.php in the actual URL. In other words, -# a controller is by default accessed at /app.php/my/controller, but will then -# be accessible at either /app.php/my/controller or just /my/controller -# -#RewriteCond %{REQUEST_FILENAME} !-f -#RewriteCond %{REQUEST_FILENAME} !-d -#RewriteRule ^(.*)$ app.php [QSA,L] - -# -# If symbolic links are not already being followed, -# uncomment the line below. -# http://anothersysadmin.wordpress.com/2008/06/10/mod_rewrite-forbidden-403-with-apache-228/ -# -#Options +FollowSymLinks - +# Order Allow,Deny From 53caf83233c962adbb68dcfb0f8172ebf788b8f7 Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 18 Nov 2012 13:35:04 -0500 Subject: [PATCH 1032/1142] [feature/controller] Remove now-unused code PHPBB3-10864 --- phpBB/includes/functions.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 17fc16ef86..02a9e33f2a 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -7,9 +7,6 @@ * */ -use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; -use Symfony\Component\Routing\RequestContext; - /** * @ignore */ @@ -2338,7 +2335,7 @@ function phpbb_on_page($template, $user, $base_url, $num_items, $per_page, $star function append_sid($url, $params = false, $is_amp = true, $session_id = false) { global $_SID, $_EXTRA_URL, $phpbb_hook; - global $phpbb_dispatcher, $phpbb_root_path, $config, $symfony_request; + global $phpbb_dispatcher; if ($params === '' || (is_array($params) && empty($params))) { From 50a96a2a2d25734e3df451b0f821817213f085e6 Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 18 Nov 2012 13:40:24 -0500 Subject: [PATCH 1033/1142] [feature/controller] Update routing documentation for using query string PHPBB3-10864 --- phpBB/config/routing.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/config/routing.yml b/phpBB/config/routing.yml index f6f728fa47..d8e890d063 100644 --- a/phpBB/config/routing.yml +++ b/phpBB/config/routing.yml @@ -4,6 +4,6 @@ # pattern: /foo # defaults: { _controller: foo_sevice:method } # -# The above will be accessed via app.php/foo and it will instantiate the -# "foo_service" service and call the "method" method. +# The above will be accessed via app.php?controller=foo and it will +# instantiate the "foo_service" service and call the "method" method. # From 440c66267ef768888617c211c7f05a5fd25e2378 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 18 Nov 2012 14:15:23 -0500 Subject: [PATCH 1034/1142] [ticket/11202] Add response assertions to file upload functional test. PHPBB3-11202 --- tests/functional/fileupload_form_test.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/functional/fileupload_form_test.php b/tests/functional/fileupload_form_test.php index f7267fa659..3db389b4f9 100644 --- a/tests/functional/fileupload_form_test.php +++ b/tests/functional/fileupload_form_test.php @@ -64,6 +64,9 @@ class phpbb_functional_fileupload_form_test extends phpbb_functional_test_case public function test_valid_file() { $crawler = $this->upload_file('valid.jpg', 'image/jpeg'); + $this->assert_response_success(); + # error message + $this->assertNotContains('

' . $this->lang('INFORMATION') . '

', $this->client->getResponse()->getContent()); $this->assertContains($this->lang('POSTED_ATTACHMENTS'), $crawler->filter('#postform h3')->eq(1)->text()); } } From 7ec94208c4096b752e77503ef53382e126b7dab5 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 18 Nov 2012 14:32:48 -0500 Subject: [PATCH 1035/1142] [ticket/11202] Fix comment char, use more descriptive comment. PHPBB3-11202 --- tests/functional/fileupload_form_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/fileupload_form_test.php b/tests/functional/fileupload_form_test.php index 3db389b4f9..99afcfdc3d 100644 --- a/tests/functional/fileupload_form_test.php +++ b/tests/functional/fileupload_form_test.php @@ -65,7 +65,7 @@ class phpbb_functional_fileupload_form_test extends phpbb_functional_test_case { $crawler = $this->upload_file('valid.jpg', 'image/jpeg'); $this->assert_response_success(); - # error message + // ensure there was no error message rendered $this->assertNotContains('

' . $this->lang('INFORMATION') . '

', $this->client->getResponse()->getContent()); $this->assertContains($this->lang('POSTED_ATTACHMENTS'), $crawler->filter('#postform h3')->eq(1)->text()); } From 60c0a1dd2ac2c733a670093ad440e3ba6be3be4d Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 18 Nov 2012 15:51:05 -0500 Subject: [PATCH 1036/1142] [feature/controller] Don't use $user->lang() before container compilation PHPBB3-10864 --- phpBB/includes/di/pass/kernel_pass.php | 7 +++---- phpBB/language/en/app.php | 5 ----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/phpBB/includes/di/pass/kernel_pass.php b/phpBB/includes/di/pass/kernel_pass.php index d186ff2767..a701ebcfa6 100644 --- a/phpBB/includes/di/pass/kernel_pass.php +++ b/phpBB/includes/di/pass/kernel_pass.php @@ -29,7 +29,6 @@ class phpbb_di_pass_kernel_pass implements CompilerPassInterface public function process(ContainerBuilder $container) { $definition = $container->getDefinition('dispatcher'); - $user = $container->get('user'); foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $events) { @@ -39,12 +38,12 @@ class phpbb_di_pass_kernel_pass implements CompilerPassInterface if (!isset($event['event'])) { - throw new InvalidArgumentException($user->lang('NO_EVENT_ATTRIBUTE', $id)); + throw new InvalidArgumentException(sprintf('Service "%1$s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); } if (!isset($event['method'])) { - throw new InvalidArgumentException($user->lang('NO_METHOD_ATTRIBUTE', $id)); + throw new InvalidArgumentException(sprintf('Service "%1$s" must define the "method" attribute on "kernel.event_listener" tags.', $id)); } $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); @@ -60,7 +59,7 @@ class phpbb_di_pass_kernel_pass implements CompilerPassInterface $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; if (!$refClass->implementsInterface($interface)) { - throw new InvalidArgumentException($user->lang('SUBSCRIBER_WRONG_TYPE', $id, $interface)); + throw new InvalidArgumentException(sprintf('Service "%1$s" must implement interface "%2$s".', $id, $interface)); } $definition->addMethodCall('addSubscriberService', array($id, $class)); diff --git a/phpBB/language/en/app.php b/phpBB/language/en/app.php index 2cbeaa2cba..2d67246369 100644 --- a/phpBB/language/en/app.php +++ b/phpBB/language/en/app.php @@ -47,11 +47,6 @@ $lang = array_merge($lang, array( 'CONTROLLER_SERVICE_UNDEFINED' => 'The service for controller "%s" is not defined in ./config/services.yml.', 'CONTROLLER_RETURN_TYPE_INVALID' => 'The controller object %s must return a Symfony\Component\HttpFoundation\Response object.', - // Event Listener/Subscriber error messages - 'NO_EVENT_ATTRIBUTE' => 'Service "%1$s" must define the "event" attribute on "kernel.event_listener" tags.', - 'NO_METHOD_ATTRIBUTE' => 'Service "%1$s" must define the "method" attribute on "kernel.event_listener" tags.', - 'SUBSCRIBER_WRONG_TYPE' => 'Service "%1$s" must implement interface "%2$s".', - // Core error controller messages 'PAGE_NOT_FOUND_ERROR' => 'The page you have requested does not exist.', 'NOT_AUTHORISED_ERROR' => 'You do not have permission to access this page.', From 2f50d656481dca3c2aa28cd5035ce0d44e5c4977 Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 18 Nov 2012 15:51:32 -0500 Subject: [PATCH 1037/1142] [feature/controller] Remove unused language strings PHPBB3-10864 --- phpBB/language/en/app.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/phpBB/language/en/app.php b/phpBB/language/en/app.php index 2d67246369..cb56015c30 100644 --- a/phpBB/language/en/app.php +++ b/phpBB/language/en/app.php @@ -46,10 +46,4 @@ $lang = array_merge($lang, array( 'CONTROLLER_SERVICE_NOT_GIVEN' => 'The controller "%s" must have a service specified in ./config/routing.yml.', 'CONTROLLER_SERVICE_UNDEFINED' => 'The service for controller "%s" is not defined in ./config/services.yml.', 'CONTROLLER_RETURN_TYPE_INVALID' => 'The controller object %s must return a Symfony\Component\HttpFoundation\Response object.', - - // Core error controller messages - 'PAGE_NOT_FOUND_ERROR' => 'The page you have requested does not exist.', - 'NOT_AUTHORISED_ERROR' => 'You do not have permission to access this page.', - 'NOT_AUTHENTICATED_ERROR' => 'You must log in to access this page.', - 'INTERNAL_SERVER_ERROR_ERROR' => 'An unknown error occured.', )); From 0f4f81b0966e29b5aaae5bf94e46260474ec0cb2 Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 18 Nov 2012 15:52:35 -0500 Subject: [PATCH 1038/1142] [feature/controller] Create Symfony Request in new function PHPBB3-10864 --- phpBB/app.php | 6 ++---- phpBB/includes/functions.php | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/phpBB/app.php b/phpBB/app.php index 13bf3f0be1..c085f966c8 100644 --- a/phpBB/app.php +++ b/phpBB/app.php @@ -7,8 +7,6 @@ * */ -use Symfony\Component\HttpFoundation\Request; - /** */ @@ -28,9 +26,9 @@ $user->setup('app'); // Until we fix the issue with relative paths, we have to fake path info to // allow urls like app.php?controller=foo/bar -$controller = $request->variable('controller', '', false, phpbb_request_interface::GET); +$controller = $request->variable('controller', ''); $uri = '/' . $controller; -$symfony_request = Request::create($uri); +$symfony_request = phpbb_create_symfony_request($uri, $request); $http_kernel = $phpbb_container->get('http_kernel'); $response = $http_kernel->handle($symfony_request); diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 02a9e33f2a..820d96c9aa 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -7,6 +7,8 @@ * */ +use Symfony\Component\HttpFoundation\Request; + /** * @ignore */ @@ -5430,3 +5432,40 @@ function phpbb_to_numeric($input) { return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; } + +/** +* Create a Symfony Request object from a given URI and phpbb_request object +* +* @param string $uri Request URI +* @param phpbb_request $request Request object +* @return Request A Symfony Request object +*/ +function phpbb_create_symfony_request($uri, phpbb_request $request) +{ + $request_method = $request->server('REQUEST_METHOD'); + $parameter_names = array(); + $parameter_names['request'] = array_merge( + $request->variable_names(phpbb_request_interface::GET), + // POST overwrites duplicated GET parameters + $request->variable_names(phpbb_request_interface::POST) + ); + $parameter_names['server'] = $request->variable_names(phpbb_request_interface::SERVER); + $parameter_names['files'] = $request->variable_names(phpbb_request_interface::FILES); + $parameter_names['cookie'] = $request->variable_names(phpbb_request_interface::COOKIE); + + $parameters = array( + 'request' => array(), + 'cookie' => array(), + 'files' => array(), + 'server' => array(), + ); + foreach ($parameter_names as $type => $names) + { + foreach ($names as $name) + { + $parameters[$type][$name] = $request->variable($name, ''); + } + } + + return Request::create($uri, $request_method, $parameters['request'], $parameters['cookie'], $parameters['files'], $parameters['server']); +} From e2bf66d0658ae7d7bb253083b73d5769c117746a Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 18 Nov 2012 15:58:47 -0500 Subject: [PATCH 1039/1142] [feature/controller] Add documentation about input being HTML-escaped PHPBB3-10864 --- phpBB/includes/functions.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 820d96c9aa..cdc05ca649 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5436,6 +5436,9 @@ function phpbb_to_numeric($input) /** * Create a Symfony Request object from a given URI and phpbb_request object * +* Note that everything passed into the Request object has already been HTML +* escaped by the phpbb_request object. +* * @param string $uri Request URI * @param phpbb_request $request Request object * @return Request A Symfony Request object From 41a95d2c646aba8d6a66ee046c532a51a9022784 Mon Sep 17 00:00:00 2001 From: Patrick Webster Date: Sun, 18 Nov 2012 20:38:58 -0600 Subject: [PATCH 1040/1142] [ticket/11219] Update sequence values after loading fixtures If a value is provide for an auto_increment type of column, certain DBMSes do not update their internal sequencers. If a row is inserted later, it can be given an ID that is already in use, resulting in an error. The database test cases now resynchronise the sequencers before the tests are run. PHPBB3-11219 --- .../phpbb_database_test_case.php | 10 ++ ...phpbb_database_test_connection_manager.php | 129 ++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/tests/test_framework/phpbb_database_test_case.php b/tests/test_framework/phpbb_database_test_case.php index 75a3c0944b..0916679108 100644 --- a/tests/test_framework/phpbb_database_test_case.php +++ b/tests/test_framework/phpbb_database_test_case.php @@ -28,6 +28,16 @@ abstract class phpbb_database_test_case extends PHPUnit_Extensions_Database_Test ); } + protected function setUp() + { + parent::setUp(); + + $config = $this->get_database_config(); + $manager = $this->create_connection_manager($config); + $manager->connect(); + $manager->post_setup_synchronisation(); + } + public function createXMLDataSet($path) { $db_config = $this->get_database_config(); diff --git a/tests/test_framework/phpbb_database_test_connection_manager.php b/tests/test_framework/phpbb_database_test_connection_manager.php index a43215bcf2..cae1720587 100644 --- a/tests/test_framework/phpbb_database_test_connection_manager.php +++ b/tests/test_framework/phpbb_database_test_connection_manager.php @@ -426,4 +426,133 @@ class phpbb_database_test_connection_manager $this->pdo->exec($query); } } + + /** + * Performs synchronisations on the database after a fixture has been loaded + */ + public function post_setup_synchronisation() + { + $this->ensure_connected(__METHOD__); + $queries = array(); + + switch ($this->config['dbms']) + { + case 'oracle': + // Get all of the information about the sequences + $sql = "SELECT t.table_name, tc.column_name, d.referenced_name as sequence_name + FROM USER_TRIGGERS t + JOIN USER_DEPENDENCIES d on d.name = t.trigger_name + JOIN USER_TRIGGER_COLS tc on (tc.trigger_name = t.trigger_name) + WHERE d.referenced_type = 'SEQUENCE' + AND d.type = 'TRIGGER'"; + $result = $this->pdo->query($sql); + + while ($row = $result->fetch(PDO::FETCH_ASSOC)) + { + // Get the current max value of the table + $sql = "SELECT MAX({$row['COLUMN_NAME']}) + 1 FROM {$row['TABLE_NAME']}"; + + $max_result = $this->pdo->query($sql); + $max_row = $max_result->fetch(PDO::FETCH_NUM); + + if (!$max_row) + { + continue; + } + + $maxval = current($max_row); + if ($maxval == null) + { + $maxval = 1; + } + + // Get the sequence's next value + $sql = "SELECT {$row['SEQUENCE_NAME']}.nextval FROM dual"; + try + { + $nextval_result = $this->pdo->query($sql); + } + catch (PDOException $e) + { + // If we catch an exception here it's because the sequencer hasn't been initialized yet. + // If the table hasn't been used, then there's nothing to do. + continue; + } + $nextval_row = $nextval_result->fetch(PDO::FETCH_NUM); + + if ($nextval_row) + { + $nextval = current($nextval_row); + + // Make sure we aren't setting the new increment to zero. + if ($maxval != $nextval) + { + // This is a multi-step process. First we need to get the sequence back into position. + // That means either advancing it or moving it backwards. Sequences have a minimum value + // of 1, so you cannot go past that. Once the offset it determined, you have to request + // the next sequence value to actually move the pointer into position. So if you're at 21 + // and need to be back at 1, set the incrementer to -20. When it's requested, it'll give + // you 1. Then we have to set the increment amount back to 1 to resume normal behavior. + + // Move the sequence to the correct position. + $sql = "ALTER SEQUENCE {$row['SEQUENCE_NAME']} INCREMENT BY " . ($maxval - $nextval); + $this->pdo->exec($sql); + + // Select the next value to actually update the sequence values + $sql = "SELECT {$row['SEQUENCE_NAME']}.nextval FROM dual"; + $this->pdo->query($sql); + + // Reset the sequence's increment amount + $sql = "ALTER SEQUENCE {$row['SEQUENCE_NAME']} INCREMENT BY 1"; + $this->pdo->exec($sql); + } + } + } + break; + + case 'postgres': + // First get the sequences + $sequences = array(); + $sql = "SELECT relname FROM pg_class WHERE relkind = 'S'"; + $result = $this->pdo->query($sql); + while ($row = $result->fetch(PDO::FETCH_ASSOC)) + { + $sequences[] = $row['relname']; + } + + // Now get the name of the column using it + foreach ($sequences as $sequence) + { + $table = str_replace('_seq', '', $sequence); + $sql = "SELECT column_name FROM information_schema.columns + WHERE table_name = '$table' + AND column_default = 'nextval(''$sequence''::regclass)'"; + $result = $this->pdo->query($sql); + $row = $result->fetch(PDO::FETCH_ASSOC); + + // Finally, set the new sequence value + if ($row) + { + $column = $row['column_name']; + + // Get the old value if it exists, or use 1 if it doesn't + $sql = "SELECT COALESCE((SELECT MAX({$column}) + 1 FROM {$table}), 1) AS val"; + $result = $this->pdo->query($sql); + $row = $result->fetch(PDO::FETCH_ASSOC); + + if ($row) + { + // The last parameter is false so that the system doesn't increment it again + $queries[] = "SELECT SETVAL('{$sequence}', {$row['val']}, false)"; + } + } + } + break; + } + + foreach ($queries as $query) + { + $this->pdo->exec($query); + } + } } From a7404409a8376e7db9f295e5cf598ccee59523b5 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 19 Nov 2012 13:49:04 +0100 Subject: [PATCH 1041/1142] [ticket/11219] Add unit test for inserting into a sequence column PHPBB3-11219 --- tests/dbal/write_sequence_test.php | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/dbal/write_sequence_test.php diff --git a/tests/dbal/write_sequence_test.php b/tests/dbal/write_sequence_test.php new file mode 100644 index 0000000000..d2c30b4e89 --- /dev/null +++ b/tests/dbal/write_sequence_test.php @@ -0,0 +1,55 @@ +createXMLDataSet(dirname(__FILE__).'/fixtures/three_users.xml'); + } + + static public function write_sequence_data() + { + return array( + array( + 'ticket/11219', + 4, + ), + ); + } + + /** + * @dataProvider write_sequence_data + */ + public function test_write_sequence($username, $expected) + { + $db = $this->new_dbal(); + + $sql = 'INSERT INTO phpbb_users ' . $db->sql_build_array('INSERT', array( + 'username' => $username, + 'username_clean' => $username, + 'user_permissions' => '', + 'user_sig' => '', + 'user_occ' => '', + 'user_interests' => '', + )); + $db->sql_query($sql); + + $this->assertEquals($expected, $db->sql_nextid()); + + $sql = "SELECT user_id + FROM phpbb_users + WHERE username_clean = '" . $db->sql_escape($username) . "'"; + $result = $db->sql_query_limit($sql, 1); + + $this->assertEquals($expected, $db->sql_fetchfield('user_id')); + } +} From 30043502814cd42d824dc1d6bcb25bebc60adbed Mon Sep 17 00:00:00 2001 From: David King Date: Mon, 19 Nov 2012 11:47:42 -0500 Subject: [PATCH 1042/1142] [feature/controller] Correctly create Symfony object from globals PHPBB3-10864 --- phpBB/includes/functions.php | 62 ++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index cdc05ca649..ee147969f9 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5434,41 +5434,47 @@ function phpbb_to_numeric($input) } /** -* Create a Symfony Request object from a given URI and phpbb_request object +* Create a Symfony Request object from phpbb_request object * -* Note that everything passed into the Request object has already been HTML -* escaped by the phpbb_request object. -* -* @param string $uri Request URI * @param phpbb_request $request Request object * @return Request A Symfony Request object */ -function phpbb_create_symfony_request($uri, phpbb_request $request) +function phpbb_create_symfony_request(phpbb_request $request) { - $request_method = $request->server('REQUEST_METHOD'); - $parameter_names = array(); - $parameter_names['request'] = array_merge( - $request->variable_names(phpbb_request_interface::GET), - // POST overwrites duplicated GET parameters - $request->variable_names(phpbb_request_interface::POST) - ); - $parameter_names['server'] = $request->variable_names(phpbb_request_interface::SERVER); - $parameter_names['files'] = $request->variable_names(phpbb_request_interface::FILES); - $parameter_names['cookie'] = $request->variable_names(phpbb_request_interface::COOKIE); + // This function is meant to sanitize the global input arrays + $sanitizer = function(&$value, $key) { + $type_cast_helper = new phpbb_request_type_cast_helper(); + $type_cast_helper->set_var($value, $value, gettype($value), true); + }; - $parameters = array( - 'request' => array(), - 'cookie' => array(), - 'files' => array(), - 'server' => array(), - ); - foreach ($parameter_names as $type => $names) + // We need to re-enable the super globals so we can access them here + $request->enable_super_globals(); + $get_parameters = $_GET; + $post_parameters = $_POST; + $server_parameters = $_SERVER; + $files_parameters = $_FILES; + $cookie_parameters = $_COOKIE; + // And now disable them again for security + $request->disable_super_globals(); + + array_walk_recursive($get_parameters, $sanitizer); + array_walk_recursive($post_parameters, $sanitizer); + + // Until we fix the issue with relative paths, we have to fake path info + // to allow urls like app.php?controller=foo/bar + $controller = $request->variable('controller', ''); + $path_info = '/' . $controller; + $request_uri = $server_parameters['REQUEST_URI']; + + // Remove the query string from REQUEST_URI + if ($pos = strpos($request_uri, '?')) { - foreach ($names as $name) - { - $parameters[$type][$name] = $request->variable($name, ''); - } + $request_uri = substr($request_uri, 0, $pos); } - return Request::create($uri, $request_method, $parameters['request'], $parameters['cookie'], $parameters['files'], $parameters['server']); + // Add the path info (i.e. controller route) to the REQUEST_URI + $server_parameters['REQUEST_URI'] = $request_uri . $path_info; + $server_parameters['SCRIPT_NAME'] = ''; + + return new Request($get_parameters, $post_parameters, array(), $cookie_parameters, $files_parameters, $server_parameters); } From f8614bfc84ba9b9cc814b8f78e343005620f18f8 Mon Sep 17 00:00:00 2001 From: David King Date: Mon, 19 Nov 2012 12:37:20 -0500 Subject: [PATCH 1043/1142] [feature/controller] Check for proper status codes from controllers PHPBB3-10864 --- phpBB/app.php | 7 +------ .../includes/event/kernel_exception_subscriber.php | 9 +++++++-- tests/functional/extension_controller_test.php | 13 ++++++++++++- .../fixtures/ext/foo/bar/config/routing.yml | 4 ++++ .../fixtures/ext/foo/bar/controller/controller.php | 5 +++++ 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/phpBB/app.php b/phpBB/app.php index c085f966c8..d93208d585 100644 --- a/phpBB/app.php +++ b/phpBB/app.php @@ -24,12 +24,7 @@ $user->session_begin(); $auth->acl($user->data); $user->setup('app'); -// Until we fix the issue with relative paths, we have to fake path info to -// allow urls like app.php?controller=foo/bar -$controller = $request->variable('controller', ''); -$uri = '/' . $controller; -$symfony_request = phpbb_create_symfony_request($uri, $request); - +$symfony_request = phpbb_create_symfony_request($request); $http_kernel = $phpbb_container->get('http_kernel'); $response = $http_kernel->handle($symfony_request); $response->send(); diff --git a/phpBB/includes/event/kernel_exception_subscriber.php b/phpBB/includes/event/kernel_exception_subscriber.php index e2668d4560..e843df2e68 100644 --- a/phpBB/includes/event/kernel_exception_subscriber.php +++ b/phpBB/includes/event/kernel_exception_subscriber.php @@ -18,6 +18,7 @@ if (!defined('IN_PHPBB')) use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpFoundation\Response; class phpbb_event_kernel_exception_subscriber implements EventSubscriberInterface @@ -56,9 +57,11 @@ class phpbb_event_kernel_exception_subscriber implements EventSubscriberInterfac { page_header($this->user->lang('INFORMATION')); + $exception = $event->getException(); + $this->template->assign_vars(array( 'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), - 'MESSAGE_TEXT' => $event->getException()->getMessage(), + 'MESSAGE_TEXT' => $exception->getMessage(), )); $this->template->set_filenames(array( @@ -67,7 +70,9 @@ class phpbb_event_kernel_exception_subscriber implements EventSubscriberInterfac page_footer(true, false, false); - $response = new Response($this->template->assign_display('body'), 404); + + $status_code = $exception instanceof NotFoundHttpException ? $exception->getStatusCode() : 500; + $response = new Response($this->template->assign_display('body'), $status_code); $event->setResponse($response); } diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index 482fe9dfd3..c7b585354e 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -116,11 +116,20 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c { $this->phpbb_extension_manager->enable('foo/bar'); $crawler = $this->request('GET', 'app.php?controller=foo/baz'); - $this->assertEquals(404, $this->client->getResponse()->getStatus()); + $this->assertEquals(500, $this->client->getResponse()->getStatus()); $this->assertContains('Missing value for argument #1: test in class phpbb_ext_foo_bar_controller:baz', $crawler->filter('body')->text()); $this->phpbb_extension_manager->purge('foo/bar'); } + public function test_exception_thrown_status_code() + { + $this->phpbb_extension_manager->enable('foo/bar'); + $crawler = $this->request('GET', 'app.php?controller=foo/exception'); + $this->assertEquals(500, $this->client->getResponse()->getStatus()); + $this->assertContains('Exception thrown from foo/exception route', $crawler->filter('body')->text()); + $this->phpbb_extension_manager->purge('foo/bar'); + } + /** * Check the error produced by extension at ./ext/does/not/exist. * @@ -133,6 +142,8 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c public function test_error_ext_disabled_or_404() { $crawler = $this->request('GET', 'app.php?controller=does/not/exist'); + // This is 500 response because the exception is thrown from within Symfony + // and does not provide a exception code, so we assign it 500 by default $this->assertEquals(404, $this->client->getResponse()->getStatus()); $this->assertContains('No route found for "GET /does/not/exist"', $crawler->filter('body')->text()); } diff --git a/tests/functional/fixtures/ext/foo/bar/config/routing.yml b/tests/functional/fixtures/ext/foo/bar/config/routing.yml index 123f5f1b73..7eb604f746 100644 --- a/tests/functional/fixtures/ext/foo/bar/config/routing.yml +++ b/tests/functional/fixtures/ext/foo/bar/config/routing.yml @@ -9,3 +9,7 @@ foo_baz_controller: foo_template_controller: pattern: /foo/template defaults: { _controller: foo_bar.controller:template } + +foo_exception_controller: + pattern: /foo/foo_exception + defaults: { _controller: foo_bar.controller:exception } diff --git a/tests/functional/fixtures/ext/foo/bar/controller/controller.php b/tests/functional/fixtures/ext/foo/bar/controller/controller.php index 50ea5d034b..5a91b5f681 100644 --- a/tests/functional/fixtures/ext/foo/bar/controller/controller.php +++ b/tests/functional/fixtures/ext/foo/bar/controller/controller.php @@ -27,4 +27,9 @@ class phpbb_ext_foo_bar_controller return $this->helper->render('foo_bar_body.html'); } + + public function exception() + { + throw new phpbb_controller_exception('Exception thrown from foo/exception route'); + } } From 01ec6085939d74e6a37c3ef041434db1c4b8f3e4 Mon Sep 17 00:00:00 2001 From: David King Date: Mon, 19 Nov 2012 12:55:15 -0500 Subject: [PATCH 1044/1142] [feature/controller] Fix comments, check against more general HttpException PHPBB3-10864 --- phpBB/includes/event/kernel_exception_subscriber.php | 4 ++-- tests/functional/extension_controller_test.php | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/event/kernel_exception_subscriber.php b/phpBB/includes/event/kernel_exception_subscriber.php index e843df2e68..f90989a74c 100644 --- a/phpBB/includes/event/kernel_exception_subscriber.php +++ b/phpBB/includes/event/kernel_exception_subscriber.php @@ -18,7 +18,7 @@ if (!defined('IN_PHPBB')) use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpFoundation\Response; class phpbb_event_kernel_exception_subscriber implements EventSubscriberInterface @@ -71,7 +71,7 @@ class phpbb_event_kernel_exception_subscriber implements EventSubscriberInterfac page_footer(true, false, false); - $status_code = $exception instanceof NotFoundHttpException ? $exception->getStatusCode() : 500; + $status_code = $exception instanceof HttpException ? $exception->getStatusCode() : 500; $response = new Response($this->template->assign_display('body'), $status_code); $event->setResponse($response); } diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index c7b585354e..f28b321942 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -121,7 +121,10 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c $this->phpbb_extension_manager->purge('foo/bar'); } - public function test_exception_thrown_status_code() + /** + * Check the status code resulting from an exception thrown by a controller + */ + public function test_exception_should_result_in_500_status_code() { $this->phpbb_extension_manager->enable('foo/bar'); $crawler = $this->request('GET', 'app.php?controller=foo/exception'); @@ -142,8 +145,6 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c public function test_error_ext_disabled_or_404() { $crawler = $this->request('GET', 'app.php?controller=does/not/exist'); - // This is 500 response because the exception is thrown from within Symfony - // and does not provide a exception code, so we assign it 500 by default $this->assertEquals(404, $this->client->getResponse()->getStatus()); $this->assertContains('No route found for "GET /does/not/exist"', $crawler->filter('body')->text()); } From 305f41cf1a540984fd7a71b61a601b1794e3bd04 Mon Sep 17 00:00:00 2001 From: David King Date: Mon, 19 Nov 2012 13:16:55 -0500 Subject: [PATCH 1045/1142] [feature/controller] Fix misnamed route for functional test PHPBB3-10864 --- tests/functional/fixtures/ext/foo/bar/config/routing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/fixtures/ext/foo/bar/config/routing.yml b/tests/functional/fixtures/ext/foo/bar/config/routing.yml index 7eb604f746..09a30a8c67 100644 --- a/tests/functional/fixtures/ext/foo/bar/config/routing.yml +++ b/tests/functional/fixtures/ext/foo/bar/config/routing.yml @@ -11,5 +11,5 @@ foo_template_controller: defaults: { _controller: foo_bar.controller:template } foo_exception_controller: - pattern: /foo/foo_exception + pattern: /foo/exception defaults: { _controller: foo_bar.controller:exception } From 1dff6d1bf988bb0d11a393fad0c0d0183366a5c9 Mon Sep 17 00:00:00 2001 From: Patrick Webster Date: Tue, 20 Nov 2012 04:40:06 -0600 Subject: [PATCH 1046/1142] [ticket/11219] Recreate Oracle sequences instead of altering them The previous method would always leave a gap between the last value and the new one due to how you have to update the sequence values. To remove gaps in all situations, the options are to alter the USER_SEQUENCES table or just drop the sequence and recreate it. The prior requires elevated priveleges and the latter can break attached objects. Since we don't attach objects to the sequences, we won't have any problems doing it for the tests. PHPBB3-11219 --- ...phpbb_database_test_connection_manager.php | 69 +++++-------------- 1 file changed, 17 insertions(+), 52 deletions(-) diff --git a/tests/test_framework/phpbb_database_test_connection_manager.php b/tests/test_framework/phpbb_database_test_connection_manager.php index cae1720587..e79a764e1d 100644 --- a/tests/test_framework/phpbb_database_test_connection_manager.php +++ b/tests/test_framework/phpbb_database_test_connection_manager.php @@ -439,10 +439,11 @@ class phpbb_database_test_connection_manager { case 'oracle': // Get all of the information about the sequences - $sql = "SELECT t.table_name, tc.column_name, d.referenced_name as sequence_name + $sql = "SELECT t.table_name, tc.column_name, d.referenced_name as sequence_name, s.increment_by, s.min_value FROM USER_TRIGGERS t - JOIN USER_DEPENDENCIES d on d.name = t.trigger_name - JOIN USER_TRIGGER_COLS tc on (tc.trigger_name = t.trigger_name) + JOIN USER_DEPENDENCIES d ON (d.name = t.trigger_name) + JOIN USER_TRIGGER_COLS tc ON (tc.trigger_name = t.trigger_name) + JOIN USER_SEQUENCES s ON (s.sequence_name = d.referenced_name) WHERE d.referenced_type = 'SEQUENCE' AND d.type = 'TRIGGER'"; $result = $this->pdo->query($sql); @@ -450,63 +451,27 @@ class phpbb_database_test_connection_manager while ($row = $result->fetch(PDO::FETCH_ASSOC)) { // Get the current max value of the table - $sql = "SELECT MAX({$row['COLUMN_NAME']}) + 1 FROM {$row['TABLE_NAME']}"; - + $sql = "SELECT MAX({$row['COLUMN_NAME']}) AS max FROM {$row['TABLE_NAME']}"; $max_result = $this->pdo->query($sql); - $max_row = $max_result->fetch(PDO::FETCH_NUM); + $max_row = $max_result->fetch(PDO::FETCH_ASSOC); if (!$max_row) { continue; } - $maxval = current($max_row); - if ($maxval == null) - { - $maxval = 1; - } + $maxval = (int)$max_row['MAX']; + $maxval++; - // Get the sequence's next value - $sql = "SELECT {$row['SEQUENCE_NAME']}.nextval FROM dual"; - try - { - $nextval_result = $this->pdo->query($sql); - } - catch (PDOException $e) - { - // If we catch an exception here it's because the sequencer hasn't been initialized yet. - // If the table hasn't been used, then there's nothing to do. - continue; - } - $nextval_row = $nextval_result->fetch(PDO::FETCH_NUM); - - if ($nextval_row) - { - $nextval = current($nextval_row); - - // Make sure we aren't setting the new increment to zero. - if ($maxval != $nextval) - { - // This is a multi-step process. First we need to get the sequence back into position. - // That means either advancing it or moving it backwards. Sequences have a minimum value - // of 1, so you cannot go past that. Once the offset it determined, you have to request - // the next sequence value to actually move the pointer into position. So if you're at 21 - // and need to be back at 1, set the incrementer to -20. When it's requested, it'll give - // you 1. Then we have to set the increment amount back to 1 to resume normal behavior. - - // Move the sequence to the correct position. - $sql = "ALTER SEQUENCE {$row['SEQUENCE_NAME']} INCREMENT BY " . ($maxval - $nextval); - $this->pdo->exec($sql); - - // Select the next value to actually update the sequence values - $sql = "SELECT {$row['SEQUENCE_NAME']}.nextval FROM dual"; - $this->pdo->query($sql); - - // Reset the sequence's increment amount - $sql = "ALTER SEQUENCE {$row['SEQUENCE_NAME']} INCREMENT BY 1"; - $this->pdo->exec($sql); - } - } + // This is not the "proper" way, but the proper way does not allow you to completely reset + // tables with no rows since you have to select the next value to make the change go into effct. + // You would have to go past the minimum value to set it correctly, but that's illegal. + // Since we have no objects attached to our sequencers (triggers aren't attached), this works fine. + $queries[] = 'DROP SEQUENCE ' . $row['SEQUENCE_NAME']; + $queries[] = "CREATE SEQUENCE {$row['SEQUENCE_NAME']} + MINVALUE {$row['MIN_VALUE']} + INCREMENT BY {$row['INCREMENT_BY']} + START WITH $maxval"; } break; From 24939c822529f179a436abfa4e4e2f1b5bcb53ec Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 4 Feb 2012 17:29:10 +0000 Subject: [PATCH 1047/1142] [ticket/10601]Move Inbox the default in private messages module Did exactly as the answer here asked: http://area51.phpbb.com/phpBB/viewtopic.php?p=234111 PHPBB3-10601 --- phpBB/includes/functions_module.php | 20 +++++++++++++++++++- phpBB/install/install_install.php | 17 +++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index ad76be9f2f..6f38dc60ce 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -759,7 +759,25 @@ class p_master } } - $u_title = $module_url . $delim . 'i=' . (($item_ary['cat']) ? $item_ary['id'] : $item_ary['name'] . (($item_ary['is_duplicate']) ? '&icat=' . $current_id : '') . '&mode=' . $item_ary['mode']); + $u_title = $module_url . $delim . 'i='; + // if the item has a name use it, else use its id + if (empty($item_ary['name'])) + { + $u_title .= $item_ary['id']; + } + else + { + $u_title .= $item_ary['name']; + } + // If the item is not a category append the mode + if (!$item_ary['cat']) + { + if ($item_ary['is_duplicate']) + { + $u_title .= '&icat=' . $current_id; + } + $u_title .= '&mode=' . $item_ary['mode']; + } // Was not allowed in categories before - /*!$item_ary['cat'] && */ $u_title .= (isset($item_ary['url_extra'])) ? $item_ary['url_extra'] : ''; diff --git a/phpBB/install/install_install.php b/phpBB/install/install_install.php index f80b8b5661..d4eba6eefd 100644 --- a/phpBB/install/install_install.php +++ b/phpBB/install/install_install.php @@ -1478,8 +1478,13 @@ class install_install extends module foreach ($this->module_categories[$module_class] as $cat_name => $subs) { + $basename = ''; + if (isset($module_categories_basenames[$cat_name])) + { + $basename = $module_categories_basenames[$cat_name]; + } $module_data = array( - 'module_basename' => '', + 'module_basename' => $basename, 'module_enabled' => 1, 'module_display' => 1, 'parent_id' => 0, @@ -1507,8 +1512,13 @@ class install_install extends module { foreach ($subs as $level2_name) { + $basename = ''; + if (isset($module_categories_basenames[$level2_name])) + { + $basename = $module_categories_basenames[$level2_name]; + } $module_data = array( - 'module_basename' => '', + 'module_basename' => $basename, 'module_enabled' => 1, 'module_display' => 1, 'parent_id' => (int) $categories[$cat_name]['id'], @@ -2115,6 +2125,9 @@ class install_install extends module 'UCP_ZEBRA' => null, ), ); + var $module_categories_basenames = array( + 'UCP_PM' => 'ucp_pm', + ); var $module_extras = array( 'acp' => array( From 61842c317a0d91278ef5d9bebe0f134be1a6d8f9 Mon Sep 17 00:00:00 2001 From: David King Date: Sat, 4 Feb 2012 20:07:21 -0500 Subject: [PATCH 1048/1142] [ticket/10601] Correctly access class property PHPBB3-10601 --- phpBB/install/install_install.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/install/install_install.php b/phpBB/install/install_install.php index d4eba6eefd..4b3d78f656 100644 --- a/phpBB/install/install_install.php +++ b/phpBB/install/install_install.php @@ -1479,9 +1479,9 @@ class install_install extends module foreach ($this->module_categories[$module_class] as $cat_name => $subs) { $basename = ''; - if (isset($module_categories_basenames[$cat_name])) + if (isset($this->module_categories_basenames[$cat_name])) { - $basename = $module_categories_basenames[$cat_name]; + $basename = $this->module_categories_basenames[$cat_name]; } $module_data = array( 'module_basename' => $basename, @@ -1513,9 +1513,9 @@ class install_install extends module foreach ($subs as $level2_name) { $basename = ''; - if (isset($module_categories_basenames[$level2_name])) + if (isset($this->module_categories_basenames[$level2_name])) { - $basename = $module_categories_basenames[$level2_name]; + $basename = $this->module_categories_basenames[$level2_name]; } $module_data = array( 'module_basename' => $basename, From 81547ba980a09832240ab3523448a159f2d514e1 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 1 Aug 2012 15:54:42 +0100 Subject: [PATCH 1049/1142] [ticket/10601] Comment explaning the basename applied to categories Explain in the code where if (isset($this->module_categories_basenames[$cat_name])) and if (isset($this->module_categories_basenames[$level2_name])) exists, what does it do. PHPBB3-10601 --- phpBB/install/install_install.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpBB/install/install_install.php b/phpBB/install/install_install.php index 4b3d78f656..33d6586e48 100644 --- a/phpBB/install/install_install.php +++ b/phpBB/install/install_install.php @@ -1479,6 +1479,7 @@ class install_install extends module foreach ($this->module_categories[$module_class] as $cat_name => $subs) { $basename = ''; + // Check if this sub-category has a basename. If it has, use it. if (isset($this->module_categories_basenames[$cat_name])) { $basename = $this->module_categories_basenames[$cat_name]; @@ -1513,6 +1514,7 @@ class install_install extends module foreach ($subs as $level2_name) { $basename = ''; + // Check if this sub-category has a basename. If it has, use it. if (isset($this->module_categories_basenames[$level2_name])) { $basename = $this->module_categories_basenames[$level2_name]; From 80da19ca7c12feb2996fd9d64dbdc8cb5c3cd2d9 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 7 Nov 2012 09:13:16 +0000 Subject: [PATCH 1050/1142] [ticket/10601] Database updating code This is what is needed to update the database to comply with these code changes PHPBB3-10601 --- phpBB/install/database_update.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 377e38c423..7b20404cf2 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2749,6 +2749,33 @@ function change_database_data(&$no_updates, $version) $config->set('site_home_url', ''); $config->set('site_home_text', ''); } + + + // ticket/10601: Make inbox default. Add basename to ucp's pm category + // Check if this was already applied + $sql = 'SELECT module_id, module_basename, parent_id, left_id, right_id + FROM ' . MODULES_TABLE . ' + WHERE + module_basename = \'ucp_pm\' + ORDER BY module_id'; + $result = $db->sql_query_limit($sql, 1); + + if ($row = $db->sql_fetchrow($result)) + { + // Checking if this is not a category + if ($row['left_id'] === $row['right_id'] - 1) + { + // This update is still not applied. Applying it + + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET module_basename = \'ucp_pm\' + WHERE module_id = ' . (int)$row['parent_id']; + + _sql($sql, $errored, $error_ary); + + } + } + $db->sql_freeresult($result); break; } From 85ebbbaec471ea64f22543e006f8c160b02d503f Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 7 Nov 2012 22:26:54 +0000 Subject: [PATCH 1051/1142] [ticket/10601] Database updating code v2 Added the space after the (int) as requested PHPBB3-10601 --- phpBB/install/database_update.php | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 7b20404cf2..620af92173 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2753,29 +2753,29 @@ function change_database_data(&$no_updates, $version) // ticket/10601: Make inbox default. Add basename to ucp's pm category // Check if this was already applied - $sql = 'SELECT module_id, module_basename, parent_id, left_id, right_id - FROM ' . MODULES_TABLE . ' - WHERE - module_basename = \'ucp_pm\' - ORDER BY module_id'; - $result = $db->sql_query_limit($sql, 1); + $sql = 'SELECT module_id, module_basename, parent_id, left_id, right_id + FROM ' . MODULES_TABLE . ' + WHERE + module_basename = \'ucp_pm\' + ORDER BY module_id'; + $result = $db->sql_query_limit($sql, 1); - if ($row = $db->sql_fetchrow($result)) + if ($row = $db->sql_fetchrow($result)) + { + // Checking if this is not a category + if ($row['left_id'] === $row['right_id'] - 1) { - // Checking if this is not a category - if ($row['left_id'] === $row['right_id'] - 1) - { - // This update is still not applied. Applying it - - $sql = 'UPDATE ' . MODULES_TABLE . ' - SET module_basename = \'ucp_pm\' - WHERE module_id = ' . (int)$row['parent_id']; - - _sql($sql, $errored, $error_ary); + // This update is still not applied. Applying it - } + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET module_basename = \'ucp_pm\' + WHERE module_id = ' . (int) $row['parent_id']; + + _sql($sql, $errored, $error_ary); + } - $db->sql_freeresult($result); + } + $db->sql_freeresult($result); break; } From 1f9eaa1c56ec909bde82e1d7ad86079cd23f46bc Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Fri, 9 Nov 2012 08:51:18 +0000 Subject: [PATCH 1052/1142] [ticket/10601] Cosmetic code changes - Removed the double line before the addition - Moved the condition of the WHERE so that both are in the same line - Removed TABs from the black lines - Used double quotes instead of escaped single quotes. PHPBB3-10601 --- phpBB/install/database_update.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 620af92173..1dae3e566b 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2749,14 +2749,12 @@ function change_database_data(&$no_updates, $version) $config->set('site_home_url', ''); $config->set('site_home_text', ''); } - - + // ticket/10601: Make inbox default. Add basename to ucp's pm category // Check if this was already applied $sql = 'SELECT module_id, module_basename, parent_id, left_id, right_id FROM ' . MODULES_TABLE . ' - WHERE - module_basename = \'ucp_pm\' + WHERE module_basename = "ucp_pm" ORDER BY module_id'; $result = $db->sql_query_limit($sql, 1); @@ -2766,13 +2764,13 @@ function change_database_data(&$no_updates, $version) if ($row['left_id'] === $row['right_id'] - 1) { // This update is still not applied. Applying it - + $sql = 'UPDATE ' . MODULES_TABLE . ' SET module_basename = \'ucp_pm\' WHERE module_id = ' . (int) $row['parent_id']; - + _sql($sql, $errored, $error_ary); - + } } $db->sql_freeresult($result); From 764da977729aef331241d0cf0df77bd2e29d6256 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Nov 2012 13:41:58 +0100 Subject: [PATCH 1053/1142] [ticket/11174] include utf_tools in mysql backend when running tests include utf_tools file in the mysql search backend PHPBB3-11174 --- phpBB/includes/search/fulltext_mysql.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php index 58a4dd7d6a..cd5f0cef67 100644 --- a/phpBB/includes/search/fulltext_mysql.php +++ b/phpBB/includes/search/fulltext_mysql.php @@ -86,6 +86,14 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base $this->word_length = array('min' => $this->config['fulltext_mysql_min_word_len'], 'max' => $this->config['fulltext_mysql_max_word_len']); + /** + * Load the UTF tools + */ + if (!function_exists('utf8_strlen')) + { + include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); + } + $error = false; } From 6e8f142d3994f4568e40447d7cfd60cb5b082824 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Nov 2012 13:43:13 +0100 Subject: [PATCH 1054/1142] [ticket/11174] removes unnecessary space from word PHPBB3-11174 --- phpBB/includes/search/fulltext_mysql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php index cd5f0cef67..ff2e24aa05 100644 --- a/phpBB/includes/search/fulltext_mysql.php +++ b/phpBB/includes/search/fulltext_mysql.php @@ -238,7 +238,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } else { - $tmp_split_words[] = $word . ' '; + $tmp_split_words[] = $word; } } if ($phrase) From 6a76b85cbc741ef15c219c2c02c318bc43f29a59 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Nov 2012 13:46:15 +0100 Subject: [PATCH 1055/1142] [ticket/11174] add mysql unit tests PHPBB3-11174 --- tests/search/mysql_test.php | 155 ++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 tests/search/mysql_test.php diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php new file mode 100644 index 0000000000..d9da2dfb2d --- /dev/null +++ b/tests/search/mysql_test.php @@ -0,0 +1,155 @@ +split_words; } +} + "; + eval($code); + } + return $wrapped; +} + +class phpbb_search_mysql_test extends phpbb_database_test_case +{ + protected $db; + protected $search; + + public function getDataSet() + { + return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/posts.xml'); + } + + protected function setUp() + { + global $phpbb_root_path, $phpEx, $config, $user, $cache; + + parent::setUp(); + + // dbal uses cache + $cache = new phpbb_cache_driver_null; + + $this->db = $this->new_dbal(); + $error = null; + $class = phpbb_search_wrapper('phpbb_search_fulltext_mysql'); + $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); + } + + protected function tearDown() + { + parent::tearDown(); + } + + public function keywords() + { + return array( + // keywords + // terms + // ok + // split words + // common words + array( + 'fooo', + 'all', + true, + array('fooo'), + array(), + ), + array( + 'fooo baar', + 'all', + true, + array('fooo', 'baar'), + array(), + ), + // leading, trailing and multiple spaces + array( + ' foo bar ', + 'all', + true, + array('foo', 'bar'), + array(), + ), + // words too short + array( + 'f', + 'all', + false, + null, + // short words count as "common" words + array('f'), + ), + array( + 'f o o', + 'all', + false, + null, + array('f', 'o', 'o'), + ), + array( + 'foo -bar', + 'all', + true, + array('-bar', 'foo'), + array(), + ), + // all negative + array( + '-foo', + 'all', + false, + null, + array(), + ), + array( + '-foo -bar', + 'all', + false, + array('-foo', '-bar'), + array(), + ), + ); + } + + /** + * @dataProvider keywords + */ + public function test_split_keywords($keywords, $terms, $ok, $split_words, $common) + { + $rv = $this->search->split_keywords($keywords, $terms); + $this->assertEquals($ok, $rv); + if ($ok) + { + // only check criteria if the search is going to be performed + $this->assert_array_content_equals($split_words, $this->search->get_split_words()); + } + $this->assert_array_content_equals($common, $this->search->get_common_words()); + } + + public function assert_array_content_equals($one, $two) + { + if (sizeof(array_diff($one, $two)) || sizeof(array_diff($two, $one))) + { + // get a nice error message + $this->assertEquals($one, $two); + } + else + { + // increase assertion count + $this->assertTrue(true); + } + } +} From 615582f0dffc8d50604e3cc567e01e807a397bec Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Nov 2012 13:47:33 +0100 Subject: [PATCH 1056/1142] [ticket/11174] rename native wrapper class native wrapper class is limited to the native search backend hence renamed. the one used with mysql can be used with pgsql too. PHPBB3-11174 --- tests/search/native_test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/search/native_test.php b/tests/search/native_test.php index 66972079cf..722da9eb59 100644 --- a/tests/search/native_test.php +++ b/tests/search/native_test.php @@ -7,7 +7,7 @@ * */ -function phpbb_search_wrapper($class) +function phpbb_native_search_wrapper($class) { $wrapped = $class . '_wrapper'; if (!class_exists($wrapped)) @@ -45,7 +45,7 @@ class phpbb_search_native_test extends phpbb_database_test_case $this->db = $this->new_dbal(); $error = null; - $class = phpbb_search_wrapper('phpbb_search_fulltext_native'); + $class = phpbb_native_search_wrapper('phpbb_search_fulltext_native'); $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); } From 2d1ac34de60a15e0b9e00a30140a08b4e329099d Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Nov 2012 14:19:32 +0100 Subject: [PATCH 1057/1142] [ticket/11174] add test case for native test PHPBB3-11174 --- tests/search/native_test.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/search/native_test.php b/tests/search/native_test.php index 722da9eb59..5f6d49c26c 100644 --- a/tests/search/native_test.php +++ b/tests/search/native_test.php @@ -106,6 +106,14 @@ class phpbb_search_native_test extends phpbb_database_test_case null, array('f', 'o', 'o'), ), + array( + 'f -o -o', + 'all', + false, + null, + null, + array('f', 'o', 'o'), + ), array( 'foo -bar', 'all', From 9d597dc2ee152f448139dc708663f9a81e5cb209 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Nov 2012 16:22:10 +0100 Subject: [PATCH 1058/1142] [ticket/11174] set config values set config values and use min length as 4 for wordss in test cases PHPBB3-11174 --- tests/search/mysql_test.php | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php index d9da2dfb2d..44043da40d 100644 --- a/tests/search/mysql_test.php +++ b/tests/search/mysql_test.php @@ -42,6 +42,10 @@ class phpbb_search_mysql_test extends phpbb_database_test_case // dbal uses cache $cache = new phpbb_cache_driver_null; + // set config values + $config['fulltext_mysql_min_word_len'] = 4; + $config['fulltext_mysql_max_word_len'] = 254; + $this->db = $this->new_dbal(); $error = null; $class = phpbb_search_wrapper('phpbb_search_fulltext_mysql'); @@ -77,10 +81,10 @@ class phpbb_search_mysql_test extends phpbb_database_test_case ), // leading, trailing and multiple spaces array( - ' foo bar ', + ' fooo baar ', 'all', true, - array('foo', 'bar'), + array('fooo', 'baar'), array(), ), // words too short @@ -100,25 +104,32 @@ class phpbb_search_mysql_test extends phpbb_database_test_case array('f', 'o', 'o'), ), array( - 'foo -bar', + 'f -o -o', + 'all', + false, + null, + array('f', '-o', '-o'), + ), + array( + 'fooo -baar', 'all', true, - array('-bar', 'foo'), + array('-baar', 'fooo'), array(), ), // all negative array( - '-foo', + '-fooo', 'all', false, null, array(), ), array( - '-foo -bar', + '-fooo -baar', 'all', false, - array('-foo', '-bar'), + array('-fooo', '-baar'), array(), ), ); From db2297827d92f09e52cd2dd6f6b4613e0c210fe7 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Nov 2012 16:25:44 +0100 Subject: [PATCH 1059/1142] [ticket/11174] negation queries do not return false negation queries are split into words too and returns false in mysql search backend PHPBB3-11174 --- tests/search/mysql_test.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php index 44043da40d..56cce24c09 100644 --- a/tests/search/mysql_test.php +++ b/tests/search/mysql_test.php @@ -121,14 +121,14 @@ class phpbb_search_mysql_test extends phpbb_database_test_case array( '-fooo', 'all', - false, - null, + true, + array('-fooo'), array(), ), array( '-fooo -baar', 'all', - false, + true, array('-fooo', '-baar'), array(), ), From c725b02df8a6d8838f6e0b7d82eaa8ec8f1839d1 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Nov 2012 17:05:07 +0100 Subject: [PATCH 1060/1142] [ticket/11174] include utf_tools in postgres search backend PHPBB3-11174 --- phpBB/includes/search/fulltext_postgres.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 08f64735b6..2880610655 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -121,6 +121,14 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base } } + /** + * Load the UTF tools + */ + if (!function_exists('utf8_strlen')) + { + include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); + } + $error = false; } From d308ee8a25ec6dc7b6e23cb78e14bd1d75f05a91 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Nov 2012 17:06:05 +0100 Subject: [PATCH 1061/1142] [ticket/11174] add unit tests for postgres search backend PHPBB3-11174 --- tests/search/postgres_test.php | 155 +++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 tests/search/postgres_test.php diff --git a/tests/search/postgres_test.php b/tests/search/postgres_test.php new file mode 100644 index 0000000000..e20e6789b7 --- /dev/null +++ b/tests/search/postgres_test.php @@ -0,0 +1,155 @@ +createXMLDataSet(dirname(__FILE__) . '/fixtures/posts.xml'); + } + + protected function setUp() + { + global $phpbb_root_path, $phpEx, $config, $user, $cache; + + parent::setUp(); + + // dbal uses cache + $cache = new phpbb_cache_driver_null; + + // set config values + $config['fulltext_postgres_min_word_len'] = 4; + $config['fulltext_postgres_max_word_len'] = 254; + + if(!function_exists('phpbb_search_wrapper')) + { + include('mysql_test.' . $phpEx); + } + + $this->db = $this->new_dbal(); + $error = null; + $class = phpbb_search_wrapper('phpbb_search_fulltext_postgres'); + $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); + } + + protected function tearDown() + { + parent::tearDown(); + } + + public function keywords() + { + return array( + // keywords + // terms + // ok + // split words + // common words + array( + 'fooo', + 'all', + true, + array('fooo'), + array(), + ), + array( + 'fooo baar', + 'all', + true, + array('fooo', 'baar'), + array(), + ), + // leading, trailing and multiple spaces + array( + ' fooo baar ', + 'all', + true, + array('fooo', 'baar'), + array(), + ), + // words too short + array( + 'f', + 'all', + false, + null, + // short words count as "common" words + array('f'), + ), + array( + 'f o o', + 'all', + false, + null, + array('f', 'o', 'o'), + ), + array( + 'f -o -o', + 'all', + false, + null, + array('f', '-o', '-o'), + ), + array( + 'fooo -baar', + 'all', + true, + array('-baar', 'fooo'), + array(), + ), + // all negative + array( + '-fooo', + 'all', + true, + array('-fooo'), + array(), + ), + array( + '-fooo -baar', + 'all', + true, + array('-fooo', '-baar'), + array(), + ), + ); + } + + /** + * @dataProvider keywords + */ + public function test_split_keywords($keywords, $terms, $ok, $split_words, $common) + { + $rv = $this->search->split_keywords($keywords, $terms); + $this->assertEquals($ok, $rv); + if ($ok) + { + // only check criteria if the search is going to be performed + $this->assert_array_content_equals($split_words, $this->search->get_split_words()); + } + $this->assert_array_content_equals($common, $this->search->get_common_words()); + } + + public function assert_array_content_equals($one, $two) + { + if (sizeof(array_diff($one, $two)) || sizeof(array_diff($two, $one))) + { + // get a nice error message + $this->assertEquals($one, $two); + } + else + { + // increase assertion count + $this->assertTrue(true); + } + } +} From 3ed4fc437e2a88638d705b8f4cab23eacf39fe3c Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 27 Nov 2012 08:08:34 -0500 Subject: [PATCH 1062/1142] [ticket/11174] Move assertion definition to base class. PHPBB3-11174 --- tests/search/native_test.php | 16 ---------------- .../test_framework/phpbb_database_test_case.php | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/search/native_test.php b/tests/search/native_test.php index 5f6d49c26c..21cbde496a 100644 --- a/tests/search/native_test.php +++ b/tests/search/native_test.php @@ -175,20 +175,4 @@ class phpbb_search_native_test extends phpbb_database_test_case } $this->assert_array_content_equals($common, $this->search->get_common_words()); } - - public function assert_array_content_equals($one, $two) - { - // http://stackoverflow.com/questions/3838288/phpunit-assert-two-arrays-are-equal-but-order-of-elements-not-important - // but one array_diff is not enough! - if (sizeof(array_diff($one, $two)) || sizeof(array_diff($two, $one))) - { - // get a nice error message - $this->assertEquals($one, $two); - } - else - { - // increase assertion count - $this->assertTrue(true); - } - } } diff --git a/tests/test_framework/phpbb_database_test_case.php b/tests/test_framework/phpbb_database_test_case.php index 75a3c0944b..514619687a 100644 --- a/tests/test_framework/phpbb_database_test_case.php +++ b/tests/test_framework/phpbb_database_test_case.php @@ -141,4 +141,20 @@ abstract class phpbb_database_test_case extends PHPUnit_Extensions_Database_Test { return $matches[1] . strtoupper($matches[2]) . $matches[3]; } + + public function assert_array_content_equals($one, $two) + { + // http://stackoverflow.com/questions/3838288/phpunit-assert-two-arrays-are-equal-but-order-of-elements-not-important + // but one array_diff is not enough! + if (sizeof(array_diff($one, $two)) || sizeof(array_diff($two, $one))) + { + // get a nice error message + $this->assertEquals($one, $two); + } + else + { + // increase assertion count + $this->assertTrue(true); + } + } } From 04480ec4ae8435db37072cd976e7591a3abaafb9 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 27 Nov 2012 08:09:35 -0500 Subject: [PATCH 1063/1142] [ticket/11174] Delete copy pasting. PHPBB3-11174 --- tests/search/mysql_test.php | 14 -------------- tests/search/postgres_test.php | 14 -------------- 2 files changed, 28 deletions(-) diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php index 56cce24c09..5e5d5c9846 100644 --- a/tests/search/mysql_test.php +++ b/tests/search/mysql_test.php @@ -149,18 +149,4 @@ class phpbb_search_mysql_test extends phpbb_database_test_case } $this->assert_array_content_equals($common, $this->search->get_common_words()); } - - public function assert_array_content_equals($one, $two) - { - if (sizeof(array_diff($one, $two)) || sizeof(array_diff($two, $one))) - { - // get a nice error message - $this->assertEquals($one, $two); - } - else - { - // increase assertion count - $this->assertTrue(true); - } - } } diff --git a/tests/search/postgres_test.php b/tests/search/postgres_test.php index e20e6789b7..d3172c6457 100644 --- a/tests/search/postgres_test.php +++ b/tests/search/postgres_test.php @@ -138,18 +138,4 @@ class phpbb_search_postgres_test extends phpbb_database_test_case } $this->assert_array_content_equals($common, $this->search->get_common_words()); } - - public function assert_array_content_equals($one, $two) - { - if (sizeof(array_diff($one, $two)) || sizeof(array_diff($two, $one))) - { - // get a nice error message - $this->assertEquals($one, $two); - } - else - { - // increase assertion count - $this->assertTrue(true); - } - } } From a5900a6b1120a3d062e6d51579872bf940b13dcb Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 27 Nov 2012 08:18:39 -0500 Subject: [PATCH 1064/1142] [ticket/11174] Extract phpbb_search_test_case. PHPBB3-11174 --- tests/search/native_test.php | 21 ++------------ .../test_framework/phpbb_search_test_case.php | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 tests/test_framework/phpbb_search_test_case.php diff --git a/tests/search/native_test.php b/tests/search/native_test.php index 21cbde496a..544ab7ca17 100644 --- a/tests/search/native_test.php +++ b/tests/search/native_test.php @@ -7,24 +7,9 @@ * */ -function phpbb_native_search_wrapper($class) -{ - $wrapped = $class . '_wrapper'; - if (!class_exists($wrapped)) - { - $code = " -class $wrapped extends $class -{ - public function get_must_contain_ids() { return \$this->must_contain_ids; } - public function get_must_not_contain_ids() { return \$this->must_not_contain_ids; } -} - "; - eval($code); - } - return $wrapped; -} +require_once dirname(__FILE__) . '/../test_framework/phpbb_search_test_case.php'; -class phpbb_search_native_test extends phpbb_database_test_case +class phpbb_search_native_test extends phpbb_search_test_case { protected $db; protected $search; @@ -45,7 +30,7 @@ class phpbb_search_native_test extends phpbb_database_test_case $this->db = $this->new_dbal(); $error = null; - $class = phpbb_native_search_wrapper('phpbb_search_fulltext_native'); + $class = self::get_search_wrapper('phpbb_search_fulltext_native'); $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); } diff --git a/tests/test_framework/phpbb_search_test_case.php b/tests/test_framework/phpbb_search_test_case.php new file mode 100644 index 0000000000..8b378186df --- /dev/null +++ b/tests/test_framework/phpbb_search_test_case.php @@ -0,0 +1,28 @@ +must_contain_ids; } + public function get_must_not_contain_ids() { return \$this->must_not_contain_ids; } +} + "; + eval($code); + } + return $wrapped; + } +} From 4d1486b08cd2f1e75527ed3b54664361934258a7 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 27 Nov 2012 08:22:43 -0500 Subject: [PATCH 1065/1142] [ticket/11174] Eliminate search wrapper copy pasting. PHPBB3-11174 --- tests/search/mysql_test.php | 20 +++---------------- tests/search/postgres_test.php | 11 ++++------ .../test_framework/phpbb_search_test_case.php | 1 + 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php index 5e5d5c9846..cf89facc83 100644 --- a/tests/search/mysql_test.php +++ b/tests/search/mysql_test.php @@ -7,23 +7,9 @@ * */ -function phpbb_search_wrapper($class) -{ - $wrapped = $class . '_wrapper'; - if (!class_exists($wrapped)) - { - $code = " -class $wrapped extends $class -{ - public function get_split_words() { return \$this->split_words; } -} - "; - eval($code); - } - return $wrapped; -} +require_once dirname(__FILE__) . '/../test_framework/phpbb_search_test_case.php'; -class phpbb_search_mysql_test extends phpbb_database_test_case +class phpbb_search_mysql_test extends phpbb_search_test_case { protected $db; protected $search; @@ -48,7 +34,7 @@ class phpbb_search_mysql_test extends phpbb_database_test_case $this->db = $this->new_dbal(); $error = null; - $class = phpbb_search_wrapper('phpbb_search_fulltext_mysql'); + $class = self::get_search_wrapper('phpbb_search_fulltext_mysql'); $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); } diff --git a/tests/search/postgres_test.php b/tests/search/postgres_test.php index d3172c6457..211755c7db 100644 --- a/tests/search/postgres_test.php +++ b/tests/search/postgres_test.php @@ -7,7 +7,9 @@ * */ -class phpbb_search_postgres_test extends phpbb_database_test_case +require_once dirname(__FILE__) . '/../test_framework/phpbb_search_test_case.php'; + +class phpbb_search_postgres_test extends phpbb_search_test_case { protected $db; protected $search; @@ -30,14 +32,9 @@ class phpbb_search_postgres_test extends phpbb_database_test_case $config['fulltext_postgres_min_word_len'] = 4; $config['fulltext_postgres_max_word_len'] = 254; - if(!function_exists('phpbb_search_wrapper')) - { - include('mysql_test.' . $phpEx); - } - $this->db = $this->new_dbal(); $error = null; - $class = phpbb_search_wrapper('phpbb_search_fulltext_postgres'); + $class = self::get_search_wrapper('phpbb_search_fulltext_postgres'); $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); } diff --git a/tests/test_framework/phpbb_search_test_case.php b/tests/test_framework/phpbb_search_test_case.php index 8b378186df..418d352c17 100644 --- a/tests/test_framework/phpbb_search_test_case.php +++ b/tests/test_framework/phpbb_search_test_case.php @@ -19,6 +19,7 @@ class $wrapped extends $class { public function get_must_contain_ids() { return \$this->must_contain_ids; } public function get_must_not_contain_ids() { return \$this->must_not_contain_ids; } + public function get_split_words() { return \$this->split_words; } } "; eval($code); From 4b5e90a2bd7380d67a0aba053b2788ce7d9abd89 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 27 Nov 2012 08:45:48 -0500 Subject: [PATCH 1066/1142] [ticket/11174] Empty fixture for when we don't need any data. PHPBB3-11174 --- tests/fixtures/empty.xml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/fixtures/empty.xml diff --git a/tests/fixtures/empty.xml b/tests/fixtures/empty.xml new file mode 100644 index 0000000000..96eb1ab483 --- /dev/null +++ b/tests/fixtures/empty.xml @@ -0,0 +1,5 @@ + + + +
+
From 0c430a1f9365260502b7b293b32a34f97edeada4 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 27 Nov 2012 08:46:15 -0500 Subject: [PATCH 1067/1142] [ticket/11174] These tests do not need posts fixtures. PHPBB3-11174 --- tests/search/mysql_test.php | 2 +- tests/search/postgres_test.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php index cf89facc83..097e46d855 100644 --- a/tests/search/mysql_test.php +++ b/tests/search/mysql_test.php @@ -16,7 +16,7 @@ class phpbb_search_mysql_test extends phpbb_search_test_case public function getDataSet() { - return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/posts.xml'); + return $this->createXMLDataSet(dirname(__FILE__) . '/../fixtures/empty.xml'); } protected function setUp() diff --git a/tests/search/postgres_test.php b/tests/search/postgres_test.php index 211755c7db..b6dc5ef1a3 100644 --- a/tests/search/postgres_test.php +++ b/tests/search/postgres_test.php @@ -16,7 +16,7 @@ class phpbb_search_postgres_test extends phpbb_search_test_case public function getDataSet() { - return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/posts.xml'); + return $this->createXMLDataSet(dirname(__FILE__) . '/../fixtures/empty.xml'); } protected function setUp() From cb2d029abf2d4857fa462f46af21728afde3cd28 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 27 Nov 2012 08:56:32 -0500 Subject: [PATCH 1068/1142] [ticket/11174] Drop needless teardown functions. PHPBB3-11174 --- tests/search/mysql_test.php | 5 ----- tests/search/native_test.php | 5 ----- tests/search/postgres_test.php | 5 ----- 3 files changed, 15 deletions(-) diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php index 097e46d855..9f38ef2ef6 100644 --- a/tests/search/mysql_test.php +++ b/tests/search/mysql_test.php @@ -38,11 +38,6 @@ class phpbb_search_mysql_test extends phpbb_search_test_case $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); } - protected function tearDown() - { - parent::tearDown(); - } - public function keywords() { return array( diff --git a/tests/search/native_test.php b/tests/search/native_test.php index 544ab7ca17..53d7a1fe78 100644 --- a/tests/search/native_test.php +++ b/tests/search/native_test.php @@ -34,11 +34,6 @@ class phpbb_search_native_test extends phpbb_search_test_case $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); } - protected function tearDown() - { - parent::tearDown(); - } - public function keywords() { return array( diff --git a/tests/search/postgres_test.php b/tests/search/postgres_test.php index b6dc5ef1a3..b8c9bcfbe9 100644 --- a/tests/search/postgres_test.php +++ b/tests/search/postgres_test.php @@ -38,11 +38,6 @@ class phpbb_search_postgres_test extends phpbb_search_test_case $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); } - protected function tearDown() - { - parent::tearDown(); - } - public function keywords() { return array( From 7dcb03faf1e9c2374f5d5fd36e3b01e8f0315d73 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 27 Nov 2012 09:06:56 -0500 Subject: [PATCH 1069/1142] [ticket/11174] Delete more copy pasting. PHPBB3-11174 --- tests/search/common_test_case.php | 106 ++++++++++++++++++++++++++++++ tests/search/mysql_test.php | 97 +-------------------------- tests/search/postgres_test.php | 97 +-------------------------- 3 files changed, 110 insertions(+), 190 deletions(-) create mode 100644 tests/search/common_test_case.php diff --git a/tests/search/common_test_case.php b/tests/search/common_test_case.php new file mode 100644 index 0000000000..dd04f7048c --- /dev/null +++ b/tests/search/common_test_case.php @@ -0,0 +1,106 @@ +search->split_keywords($keywords, $terms); + $this->assertEquals($ok, $rv); + if ($ok) + { + // only check criteria if the search is going to be performed + $this->assert_array_content_equals($split_words, $this->search->get_split_words()); + } + $this->assert_array_content_equals($common, $this->search->get_common_words()); + } +} diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php index 9f38ef2ef6..e1538bc81c 100644 --- a/tests/search/mysql_test.php +++ b/tests/search/mysql_test.php @@ -7,9 +7,9 @@ * */ -require_once dirname(__FILE__) . '/../test_framework/phpbb_search_test_case.php'; +require_once dirname(__FILE__) . '/common_test_case.php'; -class phpbb_search_mysql_test extends phpbb_search_test_case +class phpbb_search_mysql_test extends phpbb_search_common_test_case { protected $db; protected $search; @@ -37,97 +37,4 @@ class phpbb_search_mysql_test extends phpbb_search_test_case $class = self::get_search_wrapper('phpbb_search_fulltext_mysql'); $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); } - - public function keywords() - { - return array( - // keywords - // terms - // ok - // split words - // common words - array( - 'fooo', - 'all', - true, - array('fooo'), - array(), - ), - array( - 'fooo baar', - 'all', - true, - array('fooo', 'baar'), - array(), - ), - // leading, trailing and multiple spaces - array( - ' fooo baar ', - 'all', - true, - array('fooo', 'baar'), - array(), - ), - // words too short - array( - 'f', - 'all', - false, - null, - // short words count as "common" words - array('f'), - ), - array( - 'f o o', - 'all', - false, - null, - array('f', 'o', 'o'), - ), - array( - 'f -o -o', - 'all', - false, - null, - array('f', '-o', '-o'), - ), - array( - 'fooo -baar', - 'all', - true, - array('-baar', 'fooo'), - array(), - ), - // all negative - array( - '-fooo', - 'all', - true, - array('-fooo'), - array(), - ), - array( - '-fooo -baar', - 'all', - true, - array('-fooo', '-baar'), - array(), - ), - ); - } - - /** - * @dataProvider keywords - */ - public function test_split_keywords($keywords, $terms, $ok, $split_words, $common) - { - $rv = $this->search->split_keywords($keywords, $terms); - $this->assertEquals($ok, $rv); - if ($ok) - { - // only check criteria if the search is going to be performed - $this->assert_array_content_equals($split_words, $this->search->get_split_words()); - } - $this->assert_array_content_equals($common, $this->search->get_common_words()); - } } diff --git a/tests/search/postgres_test.php b/tests/search/postgres_test.php index b8c9bcfbe9..6a65e6bf8f 100644 --- a/tests/search/postgres_test.php +++ b/tests/search/postgres_test.php @@ -7,9 +7,9 @@ * */ -require_once dirname(__FILE__) . '/../test_framework/phpbb_search_test_case.php'; +require_once dirname(__FILE__) . '/common_test_case.php'; -class phpbb_search_postgres_test extends phpbb_search_test_case +class phpbb_search_postgres_test extends phpbb_search_common_test_case { protected $db; protected $search; @@ -37,97 +37,4 @@ class phpbb_search_postgres_test extends phpbb_search_test_case $class = self::get_search_wrapper('phpbb_search_fulltext_postgres'); $this->search = new $class($error, $phpbb_root_path, $phpEx, null, $config, $this->db, $user); } - - public function keywords() - { - return array( - // keywords - // terms - // ok - // split words - // common words - array( - 'fooo', - 'all', - true, - array('fooo'), - array(), - ), - array( - 'fooo baar', - 'all', - true, - array('fooo', 'baar'), - array(), - ), - // leading, trailing and multiple spaces - array( - ' fooo baar ', - 'all', - true, - array('fooo', 'baar'), - array(), - ), - // words too short - array( - 'f', - 'all', - false, - null, - // short words count as "common" words - array('f'), - ), - array( - 'f o o', - 'all', - false, - null, - array('f', 'o', 'o'), - ), - array( - 'f -o -o', - 'all', - false, - null, - array('f', '-o', '-o'), - ), - array( - 'fooo -baar', - 'all', - true, - array('-baar', 'fooo'), - array(), - ), - // all negative - array( - '-fooo', - 'all', - true, - array('-fooo'), - array(), - ), - array( - '-fooo -baar', - 'all', - true, - array('-fooo', '-baar'), - array(), - ), - ); - } - - /** - * @dataProvider keywords - */ - public function test_split_keywords($keywords, $terms, $ok, $split_words, $common) - { - $rv = $this->search->split_keywords($keywords, $terms); - $this->assertEquals($ok, $rv); - if ($ok) - { - // only check criteria if the search is going to be performed - $this->assert_array_content_equals($split_words, $this->search->get_split_words()); - } - $this->assert_array_content_equals($common, $this->search->get_common_words()); - } } From 79237b60b6b234e10f14cbcb00691b5e4374fd04 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 27 Nov 2012 10:24:31 -0500 Subject: [PATCH 1070/1142] [ticket/11174] Global $cache is a cache service instance. PHPBB3-11174 --- tests/search/mysql_test.php | 2 +- tests/search/native_test.php | 2 +- tests/search/postgres_test.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php index e1538bc81c..3ba3915714 100644 --- a/tests/search/mysql_test.php +++ b/tests/search/mysql_test.php @@ -26,7 +26,7 @@ class phpbb_search_mysql_test extends phpbb_search_common_test_case parent::setUp(); // dbal uses cache - $cache = new phpbb_cache_driver_null; + $cache = new phpbb_cache_service(new phpbb_cache_driver_null); // set config values $config['fulltext_mysql_min_word_len'] = 4; diff --git a/tests/search/native_test.php b/tests/search/native_test.php index 53d7a1fe78..eeee3a44f3 100644 --- a/tests/search/native_test.php +++ b/tests/search/native_test.php @@ -26,7 +26,7 @@ class phpbb_search_native_test extends phpbb_search_test_case parent::setUp(); // dbal uses cache - $cache = new phpbb_cache_driver_null; + $cache = new phpbb_cache_service(new phpbb_cache_driver_null); $this->db = $this->new_dbal(); $error = null; diff --git a/tests/search/postgres_test.php b/tests/search/postgres_test.php index 6a65e6bf8f..9c77e0c09e 100644 --- a/tests/search/postgres_test.php +++ b/tests/search/postgres_test.php @@ -26,7 +26,7 @@ class phpbb_search_postgres_test extends phpbb_search_common_test_case parent::setUp(); // dbal uses cache - $cache = new phpbb_cache_driver_null; + $cache = new phpbb_cache_service(new phpbb_cache_driver_null); // set config values $config['fulltext_postgres_min_word_len'] = 4; From a4cc07617726bffd4c64cdebaa2e20a463990c5d Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 28 Nov 2012 19:36:13 +0000 Subject: [PATCH 1071/1142] [ticket/10601] Requested code changes - Renamed the comment to PHPBB3-10601 - Removed backslashes - Traded double quotes into single quotes inside. PHPBB3-10601 --- phpBB/install/database_update.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 1dae3e566b..eed484dfae 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2753,9 +2753,9 @@ function change_database_data(&$no_updates, $version) // ticket/10601: Make inbox default. Add basename to ucp's pm category // Check if this was already applied $sql = 'SELECT module_id, module_basename, parent_id, left_id, right_id - FROM ' . MODULES_TABLE . ' - WHERE module_basename = "ucp_pm" - ORDER BY module_id'; + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'ucp_pm' + ORDER BY module_id"; $result = $db->sql_query_limit($sql, 1); if ($row = $db->sql_fetchrow($result)) @@ -2765,9 +2765,9 @@ function change_database_data(&$no_updates, $version) { // This update is still not applied. Applying it - $sql = 'UPDATE ' . MODULES_TABLE . ' - SET module_basename = \'ucp_pm\' - WHERE module_id = ' . (int) $row['parent_id']; + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_basename = 'ucp_pm' + WHERE module_id = " . (int) $row['parent_id']; _sql($sql, $errored, $error_ary); From 1d9891588182c831aeb1ce20065f6ceaa3f892b4 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 28 Nov 2012 19:37:16 +0000 Subject: [PATCH 1072/1142] [ticket/10601] Comment to help understanding the code PHPBB3-10601 --- phpBB/includes/functions_module.php | 1 + 1 file changed, 1 insertion(+) diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index 6f38dc60ce..0d387ace6d 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -767,6 +767,7 @@ class p_master } else { + // if the category has a name, then use it. $u_title .= $item_ary['name']; } // If the item is not a category append the mode From 65253a3023a78b1068be63b91b77618e3fb2d5fd Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 29 Nov 2012 15:35:21 -0500 Subject: [PATCH 1073/1142] [ticket/11227] @return void -> @return null in develop-olympus. PHPBB3-11227 --- phpBB/develop/generate_utf_casefold.php | 2 +- phpBB/develop/generate_utf_confusables.php | 2 +- phpBB/develop/generate_utf_tables.php | 2 +- phpBB/develop/utf_normalizer_test.php | 2 +- phpBB/docs/auth_api.html | 2 +- phpBB/includes/acm/acm_apc.php | 2 +- phpBB/includes/acm/acm_eaccelerator.php | 4 ++-- phpBB/includes/acm/acm_memcache.php | 4 ++-- phpBB/includes/acm/acm_redis.php | 4 ++-- phpBB/includes/acm/acm_wincache.php | 2 +- phpBB/includes/acm/acm_xcache.php | 2 +- phpBB/includes/functions.php | 4 ++-- phpBB/includes/functions_admin.php | 2 +- phpBB/includes/questionnaire/questionnaire.php | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/phpBB/develop/generate_utf_casefold.php b/phpBB/develop/generate_utf_casefold.php index 73951cb4dc..08f67c3eb6 100644 --- a/phpBB/develop/generate_utf_casefold.php +++ b/phpBB/develop/generate_utf_casefold.php @@ -111,7 +111,7 @@ function my_var_export($var) * Download a file to the develop/ dir * * @param string $url URL of the file to download -* @return void +* @return null */ function download($url) { diff --git a/phpBB/develop/generate_utf_confusables.php b/phpBB/develop/generate_utf_confusables.php index d2ffbcfc65..fd0439a4bb 100644 --- a/phpBB/develop/generate_utf_confusables.php +++ b/phpBB/develop/generate_utf_confusables.php @@ -199,7 +199,7 @@ function my_var_export($var) * Download a file to the develop/ dir * * @param string $url URL of the file to download -* @return void +* @return null */ function download($url) { diff --git a/phpBB/develop/generate_utf_tables.php b/phpBB/develop/generate_utf_tables.php index 6fe5d68ffd..6372270b78 100644 --- a/phpBB/develop/generate_utf_tables.php +++ b/phpBB/develop/generate_utf_tables.php @@ -481,7 +481,7 @@ function my_var_export($var) * Download a file to the develop/ dir * * @param string $url URL of the file to download -* @return void +* @return null */ function download($url) { diff --git a/phpBB/develop/utf_normalizer_test.php b/phpBB/develop/utf_normalizer_test.php index 71f24a716b..b8709888fe 100644 --- a/phpBB/develop/utf_normalizer_test.php +++ b/phpBB/develop/utf_normalizer_test.php @@ -222,7 +222,7 @@ die("\n\nALL TESTS PASSED SUCCESSFULLY\n"); * Download a file to the develop/ dir * * @param string $url URL of the file to download -* @return void +* @return null */ function download($url) { diff --git a/phpBB/docs/auth_api.html b/phpBB/docs/auth_api.html index 29469c21ac..a319460360 100644 --- a/phpBB/docs/auth_api.html +++ b/phpBB/docs/auth_api.html @@ -200,7 +200,7 @@ $user_id = 2; $auth->acl_clear_prefetch($user_id); -

This method returns void.

+

This method returns null.

2.viii. acl_get_list

diff --git a/phpBB/includes/acm/acm_apc.php b/phpBB/includes/acm/acm_apc.php index 1a487f94ad..205353d3a5 100644 --- a/phpBB/includes/acm/acm_apc.php +++ b/phpBB/includes/acm/acm_apc.php @@ -33,7 +33,7 @@ class acm extends acm_memory /** * Purge cache data * - * @return void + * @return null */ function purge() { diff --git a/phpBB/includes/acm/acm_eaccelerator.php b/phpBB/includes/acm/acm_eaccelerator.php index 645067c199..ecec3ac9a5 100644 --- a/phpBB/includes/acm/acm_eaccelerator.php +++ b/phpBB/includes/acm/acm_eaccelerator.php @@ -37,7 +37,7 @@ class acm extends acm_memory /** * Purge cache data * - * @return void + * @return null */ function purge() { @@ -54,7 +54,7 @@ class acm extends acm_memory /** * Perform cache garbage collection * - * @return void + * @return null */ function tidy() { diff --git a/phpBB/includes/acm/acm_memcache.php b/phpBB/includes/acm/acm_memcache.php index e54fa36c38..70bc219952 100644 --- a/phpBB/includes/acm/acm_memcache.php +++ b/phpBB/includes/acm/acm_memcache.php @@ -71,7 +71,7 @@ class acm extends acm_memory /** * Unload the cache resources * - * @return void + * @return null */ function unload() { @@ -83,7 +83,7 @@ class acm extends acm_memory /** * Purge cache data * - * @return void + * @return null */ function purge() { diff --git a/phpBB/includes/acm/acm_redis.php b/phpBB/includes/acm/acm_redis.php index 41533eaacb..dc11ca7768 100644 --- a/phpBB/includes/acm/acm_redis.php +++ b/phpBB/includes/acm/acm_redis.php @@ -80,7 +80,7 @@ class acm extends acm_memory /** * Unload the cache resources * - * @return void + * @return null */ function unload() { @@ -92,7 +92,7 @@ class acm extends acm_memory /** * Purge cache data * - * @return void + * @return null */ function purge() { diff --git a/phpBB/includes/acm/acm_wincache.php b/phpBB/includes/acm/acm_wincache.php index 0501ab74c5..7faba4f5b6 100644 --- a/phpBB/includes/acm/acm_wincache.php +++ b/phpBB/includes/acm/acm_wincache.php @@ -32,7 +32,7 @@ class acm extends acm_memory /** * Purge cache data * - * @return void + * @return null */ function purge() { diff --git a/phpBB/includes/acm/acm_xcache.php b/phpBB/includes/acm/acm_xcache.php index d0a614660c..e3d83f8bfa 100644 --- a/phpBB/includes/acm/acm_xcache.php +++ b/phpBB/includes/acm/acm_xcache.php @@ -48,7 +48,7 @@ class acm extends acm_memory /** * Purge cache data * - * @return void + * @return null */ function purge() { diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 6e661228b7..571c863839 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2745,7 +2745,7 @@ function meta_refresh($time, $url, $disable_cd_check = false) * * @param int $code HTTP status code * @param string $message Message for the status code -* @return void +* @return null */ function send_status_line($code, $message) { @@ -4332,7 +4332,7 @@ function phpbb_optionset($bit, $set, $data) * * @param array $param Parameter array, see $param_defaults array. * -* @return void +* @return null */ function phpbb_http_login($param) { diff --git a/phpBB/includes/functions_admin.php b/phpBB/includes/functions_admin.php index 7352b3d1f3..190185cfcf 100644 --- a/phpBB/includes/functions_admin.php +++ b/phpBB/includes/functions_admin.php @@ -3343,7 +3343,7 @@ function obtain_latest_version_info($force_update = false, $warn_fail = false, $ * @param int $flag The binary flag which is OR-ed with the current column value * @param string $sql_more This string is attached to the sql query generated to update the table. * - * @return void + * @return null */ function enable_bitfield_column_flag($table_name, $column_name, $flag, $sql_more = '') { diff --git a/phpBB/includes/questionnaire/questionnaire.php b/phpBB/includes/questionnaire/questionnaire.php index cbd7638809..3268775cb6 100644 --- a/phpBB/includes/questionnaire/questionnaire.php +++ b/phpBB/includes/questionnaire/questionnaire.php @@ -71,7 +71,7 @@ class phpbb_questionnaire_data_collector /** * Collect info into the data property. * - * @return void + * @return null */ function collect() { From ee16ed7b76315f629adc1f234d6cf3ddd9552a97 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 30 Nov 2012 11:53:19 -0500 Subject: [PATCH 1074/1142] [ticket/10875] Spelling fix. PHPBB3-10875 --- phpBB/includes/cache/driver/interface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/cache/driver/interface.php b/phpBB/includes/cache/driver/interface.php index 6adc95a86a..26dfd497aa 100644 --- a/phpBB/includes/cache/driver/interface.php +++ b/phpBB/includes/cache/driver/interface.php @@ -63,7 +63,7 @@ interface phpbb_cache_driver_interface public function destroy($var_name, $table = ''); /** - * Check if a given cache entry exist + * Check if a given cache entry exists */ public function _exists($var_name); @@ -94,7 +94,7 @@ interface phpbb_cache_driver_interface public function sql_save($query, $query_result, $ttl); /** - * Ceck if a given sql query exist in cache + * Check if a given sql query exists in cache * * @param int $query_id * @return bool From 3eb15ab59e202f6d995f40da7cbee0056e73f5d1 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 30 Nov 2012 12:04:05 -0500 Subject: [PATCH 1075/1142] [ticket/10875] More documentation. PHPBB3-10875 --- phpBB/includes/cache/driver/interface.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/cache/driver/interface.php b/phpBB/includes/cache/driver/interface.php index 26dfd497aa..d403bbcd71 100644 --- a/phpBB/includes/cache/driver/interface.php +++ b/phpBB/includes/cache/driver/interface.php @@ -68,7 +68,7 @@ interface phpbb_cache_driver_interface public function _exists($var_name); /** - * Load cached sql query + * Load result of an SQL query from cache. * * @param string $query SQL query * @@ -79,7 +79,11 @@ interface phpbb_cache_driver_interface public function sql_load($query); /** - * Save sql query + * Save result of an SQL query in cache. + * + * In persistent cache stores, this function stores the query + * result to persistent storage. In other words, there is no need + * to call save() afterwards. * * @param string $query SQL query, should be used for generating storage key * @param mixed $query_result The result from dbal::sql_query, to be passed to @@ -94,7 +98,7 @@ interface phpbb_cache_driver_interface public function sql_save($query, $query_result, $ttl); /** - * Check if a given sql query exists in cache + * Check if result for a given SQL query exists in cache. * * @param int $query_id * @return bool From 1ebc6eb68bedc4d6708cb6f23959d129030cf31e Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 30 Nov 2012 12:11:52 -0500 Subject: [PATCH 1076/1142] [ticket/10875] Must return query result on failure. PHPBB3-10875 --- phpBB/includes/cache/driver/memory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php index bc08494c0c..e4da5767cf 100644 --- a/phpBB/includes/cache/driver/memory.php +++ b/phpBB/includes/cache/driver/memory.php @@ -294,7 +294,7 @@ abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base if (!preg_match('/FROM \\(?(`?\\w+`?(?: \\w+)?(?:, ?`?\\w+`?(?: \\w+)?)*)\\)?/', $query, $regs)) { // Bail out if the match fails. - return; + return $query_result; } $tables = array_map('trim', explode(',', $regs[1])); From 7bba09811c65acfd98ebf1e6626f59de7a16cbb3 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 30 Nov 2012 12:18:33 -0500 Subject: [PATCH 1077/1142] [ticket/10875] Revise sql cache test. Delete data from database before retrieving it from cache, ensuring results come from cache. PHPBB3-10875 --- tests/cache/cache_test.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/cache/cache_test.php b/tests/cache/cache_test.php index c5f5fac88c..40eef91d53 100644 --- a/tests/cache/cache_test.php +++ b/tests/cache/cache_test.php @@ -89,20 +89,26 @@ class phpbb_cache_test extends phpbb_database_test_case WHERE config_name = 'foo'"; $result = $db->sql_query($sql, 300); $first_result = $db->sql_fetchrow($result); + $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); + $this->assertEquals($expected, $first_result); $this->assertFileExists($this->cache_dir . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)) . '.php'); + $sql = "DELETE FROM phpbb_config"; + $result = $db->sql_query($sql); + $sql = "SELECT * FROM phpbb_config WHERE config_name = 'foo'"; $result = $db->sql_query($sql, 300); - $this->assertEquals($first_result, $db->sql_fetchrow($result)); + $this->assertEquals($expected, $db->sql_fetchrow($result)); $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'bar'"; - $result = $db->sql_query($sql, 300); + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql); - $this->assertNotEquals($first_result, $db->sql_fetchrow($result)); + $no_cache_result = $db->sql_fetchrow($result); + $this->assertSame(false, $no_cache_result); $db->sql_close(); } From 0c06ac466f61cdab1c647cec23ea66ef70b2ad7e Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 30 Nov 2012 12:28:13 -0500 Subject: [PATCH 1078/1142] [ticket/10875] Test for null cache driver and sql cache. PHPBB3-10875 --- tests/cache/cache_test.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/cache/cache_test.php b/tests/cache/cache_test.php index 40eef91d53..c904aa6c41 100644 --- a/tests/cache/cache_test.php +++ b/tests/cache/cache_test.php @@ -112,4 +112,33 @@ class phpbb_cache_test extends phpbb_database_test_case $db->sql_close(); } + + public function test_null_cache_sql() + { + $driver = new phpbb_cache_driver_null($this->cache_dir); + + global $db, $cache; + $db = $this->new_dbal(); + $cache = new phpbb_cache_service($driver); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql, 300); + $first_result = $db->sql_fetchrow($result); + $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); + $this->assertEquals($expected, $first_result); + + $sql = "DELETE FROM phpbb_config"; + $result = $db->sql_query($sql); + + // As null cache driver does not actually cache, + // this should return no results + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql, 300); + + $this->assertSame(false, $db->sql_fetchrow($result)); + + $db->sql_close(); + } } From e4d2ad6b2788d9c3c030382f5ad2f02b6b7f75db Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 1 Dec 2012 00:36:34 +0100 Subject: [PATCH 1079/1142] [ticket/10875] tests/cache/cache_test.php: Use single quotes where possible. PHPBB3-10875 --- tests/cache/cache_test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cache/cache_test.php b/tests/cache/cache_test.php index c904aa6c41..285af5cd0c 100644 --- a/tests/cache/cache_test.php +++ b/tests/cache/cache_test.php @@ -94,7 +94,7 @@ class phpbb_cache_test extends phpbb_database_test_case $this->assertFileExists($this->cache_dir . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)) . '.php'); - $sql = "DELETE FROM phpbb_config"; + $sql = 'DELETE FROM phpbb_config'; $result = $db->sql_query($sql); $sql = "SELECT * FROM phpbb_config @@ -128,7 +128,7 @@ class phpbb_cache_test extends phpbb_database_test_case $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); $this->assertEquals($expected, $first_result); - $sql = "DELETE FROM phpbb_config"; + $sql = 'DELETE FROM phpbb_config'; $result = $db->sql_query($sql); // As null cache driver does not actually cache, From ec4343c7447911c7e822a03c0f57fc2bb1c4ab3d Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 30 Nov 2012 23:03:06 -0500 Subject: [PATCH 1080/1142] [ticket/11227] @return void -> @return null, per coding guidelines. PHPBB3-11227 --- phpBB/includes/config/config.php | 2 +- phpBB/includes/config/db.php | 2 +- phpBB/includes/cron/manager.php | 2 +- phpBB/includes/cron/task/core/prune_all_forums.php | 2 +- phpBB/includes/cron/task/core/prune_forum.php | 4 ++-- phpBB/includes/cron/task/core/queue.php | 2 +- phpBB/includes/cron/task/core/tidy_cache.php | 2 +- phpBB/includes/cron/task/core/tidy_database.php | 2 +- phpBB/includes/cron/task/core/tidy_search.php | 2 +- phpBB/includes/cron/task/core/tidy_sessions.php | 2 +- phpBB/includes/cron/task/core/tidy_warnings.php | 2 +- phpBB/includes/cron/task/parametrized.php | 2 +- phpBB/includes/cron/task/task.php | 2 +- phpBB/includes/functions_download.php | 2 +- phpBB/includes/group_positions.php | 10 +++++----- phpBB/includes/lock/db.php | 2 +- phpBB/includes/style/resource_locator.php | 4 ++-- phpBB/includes/template/compile.php | 2 +- 18 files changed, 24 insertions(+), 24 deletions(-) diff --git a/phpBB/includes/config/config.php b/phpBB/includes/config/config.php index 12a4a418b2..4b533dd55c 100644 --- a/phpBB/includes/config/config.php +++ b/phpBB/includes/config/config.php @@ -109,7 +109,7 @@ class phpbb_config implements ArrayAccess, IteratorAggregate, Countable * @param String $key The configuration option's name * @param bool $use_cache Whether this variable should be cached or if it * changes too frequently to be efficiently cached - * @return void + * @return null */ public function delete($key, $use_cache = true) { diff --git a/phpBB/includes/config/db.php b/phpBB/includes/config/db.php index 993a764a7f..45f9f1cb21 100644 --- a/phpBB/includes/config/db.php +++ b/phpBB/includes/config/db.php @@ -96,7 +96,7 @@ class phpbb_config_db extends phpbb_config * @param String $key The configuration option's name * @param bool $use_cache Whether this variable should be cached or if it * changes too frequently to be efficiently cached - * @return void + * @return null */ public function delete($key, $use_cache = true) { diff --git a/phpBB/includes/cron/manager.php b/phpBB/includes/cron/manager.php index ccaa4f3764..84c9650830 100644 --- a/phpBB/includes/cron/manager.php +++ b/phpBB/includes/cron/manager.php @@ -54,7 +54,7 @@ class phpbb_cron_manager * * @param array|Traversable $tasks Array of instances of phpbb_cron_task * - * @return void + * @return null */ public function load_tasks($tasks) { diff --git a/phpBB/includes/cron/task/core/prune_all_forums.php b/phpBB/includes/cron/task/core/prune_all_forums.php index 252e16e57d..ee0b5f7626 100644 --- a/phpBB/includes/cron/task/core/prune_all_forums.php +++ b/phpBB/includes/cron/task/core/prune_all_forums.php @@ -50,7 +50,7 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base /** * Runs this cron task. * - * @return void + * @return null */ public function run() { diff --git a/phpBB/includes/cron/task/core/prune_forum.php b/phpBB/includes/cron/task/core/prune_forum.php index 41d60af921..fa7a761d88 100644 --- a/phpBB/includes/cron/task/core/prune_forum.php +++ b/phpBB/includes/cron/task/core/prune_forum.php @@ -70,7 +70,7 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p /** * Runs this cron task. * - * @return void + * @return null */ public function run() { @@ -138,7 +138,7 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p * * @param phpbb_request_interface $request Request object. * - * @return void + * @return null */ public function parse_parameters(phpbb_request_interface $request) { diff --git a/phpBB/includes/cron/task/core/queue.php b/phpBB/includes/cron/task/core/queue.php index c765660906..732f9c6bea 100644 --- a/phpBB/includes/cron/task/core/queue.php +++ b/phpBB/includes/cron/task/core/queue.php @@ -43,7 +43,7 @@ class phpbb_cron_task_core_queue extends phpbb_cron_task_base /** * Runs this cron task. * - * @return void + * @return null */ public function run() { diff --git a/phpBB/includes/cron/task/core/tidy_cache.php b/phpBB/includes/cron/task/core/tidy_cache.php index 6017eea561..16a45dae7c 100644 --- a/phpBB/includes/cron/task/core/tidy_cache.php +++ b/phpBB/includes/cron/task/core/tidy_cache.php @@ -40,7 +40,7 @@ class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base /** * Runs this cron task. * - * @return void + * @return null */ public function run() { diff --git a/phpBB/includes/cron/task/core/tidy_database.php b/phpBB/includes/cron/task/core/tidy_database.php index 1d256f964f..b882e7b500 100644 --- a/phpBB/includes/cron/task/core/tidy_database.php +++ b/phpBB/includes/cron/task/core/tidy_database.php @@ -43,7 +43,7 @@ class phpbb_cron_task_core_tidy_database extends phpbb_cron_task_base /** * Runs this cron task. * - * @return void + * @return null */ public function run() { diff --git a/phpBB/includes/cron/task/core/tidy_search.php b/phpBB/includes/cron/task/core/tidy_search.php index 2e5f3d79d5..fdbe31346e 100644 --- a/phpBB/includes/cron/task/core/tidy_search.php +++ b/phpBB/includes/cron/task/core/tidy_search.php @@ -54,7 +54,7 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base /** * Runs this cron task. * - * @return void + * @return null */ public function run() { diff --git a/phpBB/includes/cron/task/core/tidy_sessions.php b/phpBB/includes/cron/task/core/tidy_sessions.php index 13531aa30b..95f55235c9 100644 --- a/phpBB/includes/cron/task/core/tidy_sessions.php +++ b/phpBB/includes/cron/task/core/tidy_sessions.php @@ -40,7 +40,7 @@ class phpbb_cron_task_core_tidy_sessions extends phpbb_cron_task_base /** * Runs this cron task. * - * @return void + * @return null */ public function run() { diff --git a/phpBB/includes/cron/task/core/tidy_warnings.php b/phpBB/includes/cron/task/core/tidy_warnings.php index 8dd0674fe5..2a7798e56e 100644 --- a/phpBB/includes/cron/task/core/tidy_warnings.php +++ b/phpBB/includes/cron/task/core/tidy_warnings.php @@ -45,7 +45,7 @@ class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base /** * Runs this cron task. * - * @return void + * @return null */ public function run() { diff --git a/phpBB/includes/cron/task/parametrized.php b/phpBB/includes/cron/task/parametrized.php index 0714b2e701..5f0e46eafc 100644 --- a/phpBB/includes/cron/task/parametrized.php +++ b/phpBB/includes/cron/task/parametrized.php @@ -46,7 +46,7 @@ interface phpbb_cron_task_parametrized extends phpbb_cron_task * * @param phpbb_request_interface $request Request object. * - * @return void + * @return null */ public function parse_parameters(phpbb_request_interface $request); } diff --git a/phpBB/includes/cron/task/task.php b/phpBB/includes/cron/task/task.php index 7b08fed413..2d585df96d 100644 --- a/phpBB/includes/cron/task/task.php +++ b/phpBB/includes/cron/task/task.php @@ -31,7 +31,7 @@ interface phpbb_cron_task /** * Runs this cron task. * - * @return void + * @return null */ public function run(); diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php index b6371dbecc..fc6f1cc762 100644 --- a/phpBB/includes/functions_download.php +++ b/phpBB/includes/functions_download.php @@ -433,7 +433,7 @@ function set_modified_headers($stamp, $browser) * * @param bool $exit Whether to die or not. * -* @return void +* @return null */ function file_gc($exit = true) { diff --git a/phpBB/includes/group_positions.php b/phpBB/includes/group_positions.php index 74de3516cb..60352ed97d 100644 --- a/phpBB/includes/group_positions.php +++ b/phpBB/includes/group_positions.php @@ -104,7 +104,7 @@ class phpbb_group_positions * Addes a group by group_id * * @param int $group_id group_id of the group to be added - * @return void + * @return null */ public function add_group($group_id) { @@ -128,7 +128,7 @@ class phpbb_group_positions * * @param int $group_id group_id of the group to be deleted * @param bool $skip_group Skip setting the group to GROUP_DISABLED, to save the query, when you need to update it anyway. - * @return void + * @return null */ public function delete_group($group_id, $skip_group = false) { @@ -159,7 +159,7 @@ class phpbb_group_positions * Moves a group up by group_id * * @param int $group_id group_id of the group to be moved - * @return void + * @return null */ public function move_up($group_id) { @@ -170,7 +170,7 @@ class phpbb_group_positions * Moves a group down by group_id * * @param int $group_id group_id of the group to be moved - * @return void + * @return null */ public function move_down($group_id) { @@ -184,7 +184,7 @@ class phpbb_group_positions * @param int $delta number of steps: * - positive = move up * - negative = move down - * @return void + * @return null */ public function move($group_id, $delta) { diff --git a/phpBB/includes/lock/db.php b/phpBB/includes/lock/db.php index fa559d6887..6e94dd5a85 100644 --- a/phpBB/includes/lock/db.php +++ b/phpBB/includes/lock/db.php @@ -125,7 +125,7 @@ class phpbb_lock_db * Note: Attempting to release a lock that is already released, * that is, calling release() multiple times, is harmless. * - * @return void + * @return null */ public function release() { diff --git a/phpBB/includes/style/resource_locator.php b/phpBB/includes/style/resource_locator.php index 04beddb434..4cf767c062 100644 --- a/phpBB/includes/style/resource_locator.php +++ b/phpBB/includes/style/resource_locator.php @@ -110,7 +110,7 @@ class phpbb_style_resource_locator implements phpbb_template_locator * Typically it is one directory level deep, e.g. "template/". * * @param string $template_path Relative path to templates directory within style directories - * @return void + * @return null */ public function set_template_path($template_path) { @@ -121,7 +121,7 @@ class phpbb_style_resource_locator implements phpbb_template_locator * Sets the location of templates directory within style directories * to the default, which is "template/". * - * @return void + * @return null */ public function set_default_template_path() { diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php index 82b301c1a2..d0b3d0f115 100644 --- a/phpBB/includes/template/compile.php +++ b/phpBB/includes/template/compile.php @@ -118,7 +118,7 @@ class phpbb_template_compile * * @param resource $source_stream Source stream * @param resource $dest_stream Destination stream - * @return void + * @return null */ private function compile_stream_to_stream($source_stream, $dest_stream) { From e5e8087bebdae8c4da2e59e9afd984d6a2008caf Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 30 Nov 2012 23:03:48 -0500 Subject: [PATCH 1081/1142] [ticket/11227] @return void -> @return null in code sniffer. PHPBB3-11227 --- .../phpbb/Sniffs/Commenting/FileCommentSniff.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code_sniffer/phpbb/Sniffs/Commenting/FileCommentSniff.php b/code_sniffer/phpbb/Sniffs/Commenting/FileCommentSniff.php index ba2b40ecba..68e9e6bb86 100644 --- a/code_sniffer/phpbb/Sniffs/Commenting/FileCommentSniff.php +++ b/code_sniffer/phpbb/Sniffs/Commenting/FileCommentSniff.php @@ -35,7 +35,7 @@ class phpbb_Sniffs_Commenting_FileCommentSniff implements PHP_CodeSniffer_Sniff * @param int $stackPtr The position of the current token * in the stack passed in $tokens. * - * @return void + * @return null */ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { @@ -120,7 +120,7 @@ class phpbb_Sniffs_Commenting_FileCommentSniff implements PHP_CodeSniffer_Sniff * @param integer The stack pointer for the first comment token. * @param array(string=>array) $tags The found file doc comment tags. * - * @return void + * @return null */ protected function processPackage(PHP_CodeSniffer_File $phpcsFile, $ptr, $tags) { @@ -143,7 +143,7 @@ class phpbb_Sniffs_Commenting_FileCommentSniff implements PHP_CodeSniffer_Sniff * @param integer The stack pointer for the first comment token. * @param array(string=>array) $tags The found file doc comment tags. * - * @return void + * @return null */ protected function processVersion(PHP_CodeSniffer_File $phpcsFile, $ptr, $tags) { @@ -166,7 +166,7 @@ class phpbb_Sniffs_Commenting_FileCommentSniff implements PHP_CodeSniffer_Sniff * @param integer The stack pointer for the first comment token. * @param array(string=>array) $tags The found file doc comment tags. * - * @return void + * @return null */ protected function processCopyright(PHP_CodeSniffer_File $phpcsFile, $ptr, $tags) { @@ -189,7 +189,7 @@ class phpbb_Sniffs_Commenting_FileCommentSniff implements PHP_CodeSniffer_Sniff * @param integer The stack pointer for the first comment token. * @param array(string=>array) $tags The found file doc comment tags. * - * @return void + * @return null */ protected function processLicense(PHP_CodeSniffer_File $phpcsFile, $ptr, $tags) { From c852044d6eecc0a652800b1661491c0f9c545054 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 00:48:21 -0500 Subject: [PATCH 1082/1142] [ticket/9983] Add redis cache driver tests. In order to not overwrite data in default redis store, at least one of redis host or post must be explicitly specified. Redis cache driver constructor has been modified to accept host and port as parameters. This was not added to public API as there are more parameters being passed via global constants. PHPBB3-9983 --- phpBB/includes/cache/driver/redis.php | 27 +++++++- tests/RUNNING_TESTS.txt | 15 ++++ tests/cache/cache_test.php | 68 ++++++++++++++++++- .../phpbb_test_case_helpers.php | 19 ++++++ 4 files changed, 126 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/cache/driver/redis.php b/phpBB/includes/cache/driver/redis.php index d256b5600e..ae6f9e04f2 100644 --- a/phpBB/includes/cache/driver/redis.php +++ b/phpBB/includes/cache/driver/redis.php @@ -39,13 +39,38 @@ class phpbb_cache_driver_redis extends phpbb_cache_driver_memory var $redis; + /** + * Creates a redis cache driver. + * + * The following global constants affect operation: + * + * PHPBB_ACM_REDIS_HOST + * PHPBB_ACM_REDIS_PORT + * PHPBB_ACM_REDIS_PASSWORD + * PHPBB_ACM_REDIS_DB + * + * There are no publicly documented constructor parameters. + */ function __construct() { // Call the parent constructor parent::__construct(); $this->redis = new Redis(); - $this->redis->connect(PHPBB_ACM_REDIS_HOST, PHPBB_ACM_REDIS_PORT); + + $args = func_get_args(); + if (!empty($args)) + { + $ok = call_user_func_array(array($this->redis, 'connect'), $args); + } + else + { + $ok = $this->redis->connect(PHPBB_ACM_REDIS_HOST, PHPBB_ACM_REDIS_PORT); + } + if (!$ok) + { + trigger_error('Could not connect to redis server'); + } if (defined('PHPBB_ACM_REDIS_PASSWORD')) { diff --git a/tests/RUNNING_TESTS.txt b/tests/RUNNING_TESTS.txt index 7c2a7c3fce..11617964cb 100644 --- a/tests/RUNNING_TESTS.txt +++ b/tests/RUNNING_TESTS.txt @@ -72,6 +72,21 @@ to connect to that database in phpBB. Additionally, you will need to be running the DbUnit fork from https://github.com/phpbb/dbunit/tree/phpbb. +Redis +----- + +In order to run tests for the Redis cache driver, at least one of Redis host +or port must be specified in test configuration. This can be done via +test_config.php as follows: + + cache_dir); @@ -87,12 +87,76 @@ class phpbb_cache_test extends phpbb_database_test_case $sql = "SELECT * FROM phpbb_config WHERE config_name = 'foo'"; + + $cache_path = $this->cache_dir . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)) . '.php'; + $this->assertFileNotExists($cache_path); + $result = $db->sql_query($sql, 300); $first_result = $db->sql_fetchrow($result); $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); $this->assertEquals($expected, $first_result); - $this->assertFileExists($this->cache_dir . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)) . '.php'); + $this->assertFileExists($cache_path); + + $sql = 'DELETE FROM phpbb_config'; + $result = $db->sql_query($sql); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql, 300); + + $this->assertEquals($expected, $db->sql_fetchrow($result)); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql); + + $no_cache_result = $db->sql_fetchrow($result); + $this->assertSame(false, $no_cache_result); + + $db->sql_close(); + } + + public function test_cache_sql_redis() + { + if (!extension_loaded('redis')) + { + $this->markTestSkipped('redis extension is not loaded'); + } + + $config = phpbb_test_case_helpers::get_test_config(); + if (isset($config['redis_host']) || isset($config['redis_port'])) + { + $host = isset($config['redis_host']) ? $config['redis_host'] : 'localhost'; + $port = isset($config['redis_port']) ? $config['redis_port'] : 6379; + } + else + { + $this->markTestSkipped('Test redis host/port is not specified'); + } + $driver = new phpbb_cache_driver_redis($host, $port); + $driver->purge(); + + global $db, $cache; + $db = $this->new_dbal(); + $cache = new phpbb_cache_service($driver); + + $redis = new Redis(); + $ok = $redis->connect($host, $port); + $this->assertTrue($ok); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + + $key = $driver->key_prefix . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)); + $this->assertFalse($redis->exists($key)); + + $result = $db->sql_query($sql, 300); + $first_result = $db->sql_fetchrow($result); + $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); + $this->assertEquals($expected, $first_result); + + $this->assertTrue($redis->exists($key)); $sql = 'DELETE FROM phpbb_config'; $result = $db->sql_query($sql); diff --git a/tests/test_framework/phpbb_test_case_helpers.php b/tests/test_framework/phpbb_test_case_helpers.php index d10645a732..b56a699d1c 100644 --- a/tests/test_framework/phpbb_test_case_helpers.php +++ b/tests/test_framework/phpbb_test_case_helpers.php @@ -91,6 +91,15 @@ class phpbb_test_case_helpers { $config['phpbb_functional_url'] = $phpbb_functional_url; } + + if (isset($redis_host)) + { + $config['redis_host'] = $redis_host; + } + if (isset($redis_port)) + { + $config['redis_port'] = $redis_port; + } } if (isset($_SERVER['PHPBB_TEST_DBMS'])) @@ -113,6 +122,16 @@ class phpbb_test_case_helpers )); } + if (isset($_SERVER['PHPBB_TEST_REDIS_HOST'])) + { + $config['redis_host'] = $_SERVER['PHPBB_TEST_REDIS_HOST']; + } + + if (isset($_SERVER['PHPBB_TEST_REDIS_PORT'])) + { + $config['redis_port'] = $_SERVER['PHPBB_TEST_REDIS_PORT']; + } + return $config; } From a0d5c52eb6e8ef3a6bb44cff60b364d3a3a5bf3e Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 1 Dec 2012 09:44:51 +0000 Subject: [PATCH 1083/1142] [ticket/10601] New approach in the update algorithm - New approach in the database update algorithm PHPBB3-10601 --- phpBB/install/database_update.php | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index eed484dfae..f3136690a2 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2750,28 +2750,25 @@ function change_database_data(&$no_updates, $version) $config->set('site_home_text', ''); } - // ticket/10601: Make inbox default. Add basename to ucp's pm category - // Check if this was already applied - $sql = 'SELECT module_id, module_basename, parent_id, left_id, right_id + // PHPBB3-10601: Make inbox default. Add basename to ucp's pm category + + // Get the category wanted while checking, at the same time, if this has already been applied + $sql = 'SELECT module_id, module_basename FROM ' . MODULES_TABLE . " - WHERE module_basename = 'ucp_pm' + WHERE module_basename <> 'ucp_pm' AND + module_langname='UCP_PM' ORDER BY module_id"; $result = $db->sql_query_limit($sql, 1); if ($row = $db->sql_fetchrow($result)) { - // Checking if this is not a category - if ($row['left_id'] === $row['right_id'] - 1) - { - // This update is still not applied. Applying it + // This update is still not applied. Applying it - $sql = 'UPDATE ' . MODULES_TABLE . " - SET module_basename = 'ucp_pm' - WHERE module_id = " . (int) $row['parent_id']; + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_basename = 'ucp_pm' + WHERE module_id = " . (int) $row['module_id']; - _sql($sql, $errored, $error_ary); - - } + _sql($sql, $errored, $error_ary); } $db->sql_freeresult($result); From 314462d8352a6b5ea1fd27ce1bb21cb0a8fb1310 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 9 Nov 2012 22:36:01 +0100 Subject: [PATCH 1084/1142] [ticket/10184] Disable receiving pms for bots by default PHPBB3-10184 --- phpBB/install/database_update.php | 6 ++++++ phpBB/install/install_install.php | 1 + 2 files changed, 7 insertions(+) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 399ad3429a..8e23434b5b 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2171,6 +2171,12 @@ function change_database_data(&$no_updates, $version) } } + // Disable receiving pms for bots + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_allow_pm = 0 + WHERE user_type = ' . USER_IGNORE; + $db->sql_query($sql); + $no_updates = false; break; } diff --git a/phpBB/install/install_install.php b/phpBB/install/install_install.php index 67ebb8b1c5..0575b58d92 100644 --- a/phpBB/install/install_install.php +++ b/phpBB/install/install_install.php @@ -1859,6 +1859,7 @@ class install_install extends module 'user_timezone' => 0, 'user_dateformat' => $lang['default_dateformat'], 'user_allow_massemail' => 0, + 'user_allow_pm' => 0, ); $user_id = user_add($user_row); From ad2d560f3f6ab0232728392b2c1c946f2b07902d Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 16 Nov 2012 14:32:31 +0100 Subject: [PATCH 1085/1142] [ticket/10184] Query bots table to get the user_ids of the bots PHPBB3-10184 --- phpBB/install/database_update.php | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 8e23434b5b..983b1b46c4 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2172,10 +2172,24 @@ function change_database_data(&$no_updates, $version) } // Disable receiving pms for bots - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_allow_pm = 0 - WHERE user_type = ' . USER_IGNORE; - $db->sql_query($sql); + $sql = 'SELECT user_id + FROM ' . BOTS_TABLE; + $result = $db->sql_query($sql); + + $bot_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $bot_user_ids[] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + if (!empty($bot_user_ids)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_allow_pm = 0 + WHERE ' . $db->sql_in_set('user_id', $bot_user_ids); + _sql($sql, $errored, $error_ary); + } $no_updates = false; break; From 1ce06711811561d2e3fa3c6ba2aeac4ebffa6581 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 1 Dec 2012 10:05:20 +0000 Subject: [PATCH 1086/1142] [ticket/10601] The ORDER BY is only taking space there PHPBB3-10601 --- phpBB/install/database_update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index f3136690a2..ae6e3bd9cf 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2757,7 +2757,7 @@ function change_database_data(&$no_updates, $version) FROM ' . MODULES_TABLE . " WHERE module_basename <> 'ucp_pm' AND module_langname='UCP_PM' - ORDER BY module_id"; + "; $result = $db->sql_query_limit($sql, 1); if ($row = $db->sql_fetchrow($result)) From 1e3dff83b3e56353fd97a6581989c478e52ed892 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 15:03:44 -0500 Subject: [PATCH 1087/1142] [ticket/9983] Split cache test into per-driver files. PHPBB3-9983 --- tests/cache/cache_test.php | 208 ------------------------------ tests/cache/file_driver_test.php | 117 +++++++++++++++++ tests/cache/null_driver_test.php | 45 +++++++ tests/cache/redis_driver_test.php | 89 +++++++++++++ 4 files changed, 251 insertions(+), 208 deletions(-) delete mode 100644 tests/cache/cache_test.php create mode 100644 tests/cache/file_driver_test.php create mode 100644 tests/cache/null_driver_test.php create mode 100644 tests/cache/redis_driver_test.php diff --git a/tests/cache/cache_test.php b/tests/cache/cache_test.php deleted file mode 100644 index ad60a9077e..0000000000 --- a/tests/cache/cache_test.php +++ /dev/null @@ -1,208 +0,0 @@ -cache_dir = dirname(__FILE__) . '/../tmp/cache/'; - } - - public function getDataSet() - { - return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/config.xml'); - } - - protected function setUp() - { - parent::setUp(); - - if (file_exists($this->cache_dir)) - { - // cache directory possibly left after aborted - // or failed run earlier - $this->remove_cache_dir(); - } - $this->create_cache_dir(); - } - - protected function tearDown() - { - if (file_exists($this->cache_dir)) - { - $this->remove_cache_dir(); - } - - parent::tearDown(); - } - - private function create_cache_dir() - { - $this->get_test_case_helpers()->makedirs($this->cache_dir); - } - - private function remove_cache_dir() - { - $iterator = new DirectoryIterator($this->cache_dir); - foreach ($iterator as $file) - { - if ($file != '.' && $file != '..') - { - unlink($this->cache_dir . '/' . $file); - } - } - rmdir($this->cache_dir); - } - - public function test_cache_driver_file() - { - $driver = new phpbb_cache_driver_file($this->cache_dir); - $driver->put('test_key', 'test_value'); - $driver->save(); - - $this->assertEquals( - 'test_value', - $driver->get('test_key'), - 'File ACM put and get' - ); - } - - public function test_cache_sql_file() - { - $driver = new phpbb_cache_driver_file($this->cache_dir); - - global $db, $cache; - $db = $this->new_dbal(); - $cache = new phpbb_cache_service($driver); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - - $cache_path = $this->cache_dir . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)) . '.php'; - $this->assertFileNotExists($cache_path); - - $result = $db->sql_query($sql, 300); - $first_result = $db->sql_fetchrow($result); - $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); - $this->assertEquals($expected, $first_result); - - $this->assertFileExists($cache_path); - - $sql = 'DELETE FROM phpbb_config'; - $result = $db->sql_query($sql); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql, 300); - - $this->assertEquals($expected, $db->sql_fetchrow($result)); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql); - - $no_cache_result = $db->sql_fetchrow($result); - $this->assertSame(false, $no_cache_result); - - $db->sql_close(); - } - - public function test_cache_sql_redis() - { - if (!extension_loaded('redis')) - { - $this->markTestSkipped('redis extension is not loaded'); - } - - $config = phpbb_test_case_helpers::get_test_config(); - if (isset($config['redis_host']) || isset($config['redis_port'])) - { - $host = isset($config['redis_host']) ? $config['redis_host'] : 'localhost'; - $port = isset($config['redis_port']) ? $config['redis_port'] : 6379; - } - else - { - $this->markTestSkipped('Test redis host/port is not specified'); - } - $driver = new phpbb_cache_driver_redis($host, $port); - $driver->purge(); - - global $db, $cache; - $db = $this->new_dbal(); - $cache = new phpbb_cache_service($driver); - - $redis = new Redis(); - $ok = $redis->connect($host, $port); - $this->assertTrue($ok); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - - $key = $driver->key_prefix . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)); - $this->assertFalse($redis->exists($key)); - - $result = $db->sql_query($sql, 300); - $first_result = $db->sql_fetchrow($result); - $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); - $this->assertEquals($expected, $first_result); - - $this->assertTrue($redis->exists($key)); - - $sql = 'DELETE FROM phpbb_config'; - $result = $db->sql_query($sql); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql, 300); - - $this->assertEquals($expected, $db->sql_fetchrow($result)); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql); - - $no_cache_result = $db->sql_fetchrow($result); - $this->assertSame(false, $no_cache_result); - - $db->sql_close(); - } - - public function test_null_cache_sql() - { - $driver = new phpbb_cache_driver_null($this->cache_dir); - - global $db, $cache; - $db = $this->new_dbal(); - $cache = new phpbb_cache_service($driver); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql, 300); - $first_result = $db->sql_fetchrow($result); - $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); - $this->assertEquals($expected, $first_result); - - $sql = 'DELETE FROM phpbb_config'; - $result = $db->sql_query($sql); - - // As null cache driver does not actually cache, - // this should return no results - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql, 300); - - $this->assertSame(false, $db->sql_fetchrow($result)); - - $db->sql_close(); - } -} diff --git a/tests/cache/file_driver_test.php b/tests/cache/file_driver_test.php new file mode 100644 index 0000000000..436bd2f6bc --- /dev/null +++ b/tests/cache/file_driver_test.php @@ -0,0 +1,117 @@ +cache_dir = dirname(__FILE__) . '/../tmp/cache/'; + } + + public function getDataSet() + { + return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/config.xml'); + } + + protected function setUp() + { + parent::setUp(); + + if (file_exists($this->cache_dir)) + { + // cache directory possibly left after aborted + // or failed run earlier + $this->remove_cache_dir(); + } + $this->create_cache_dir(); + + $this->driver = new phpbb_cache_driver_file($this->cache_dir); + } + + protected function tearDown() + { + if (file_exists($this->cache_dir)) + { + $this->remove_cache_dir(); + } + + parent::tearDown(); + } + + private function create_cache_dir() + { + $this->get_test_case_helpers()->makedirs($this->cache_dir); + } + + private function remove_cache_dir() + { + $iterator = new DirectoryIterator($this->cache_dir); + foreach ($iterator as $file) + { + if ($file != '.' && $file != '..') + { + unlink($this->cache_dir . '/' . $file); + } + } + rmdir($this->cache_dir); + } + + public function test_cache_driver_file() + { + $this->driver->put('test_key', 'test_value'); + $this->driver->save(); + + $this->assertEquals( + 'test_value', + $this->driver->get('test_key'), + 'File ACM put and get' + ); + } + + public function test_cache_sql_file() + { + global $db, $cache; + $db = $this->new_dbal(); + $cache = new phpbb_cache_service($this->driver); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + + $cache_path = $this->cache_dir . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)) . '.php'; + $this->assertFileNotExists($cache_path); + + $result = $db->sql_query($sql, 300); + $first_result = $db->sql_fetchrow($result); + $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); + $this->assertEquals($expected, $first_result); + + $this->assertFileExists($cache_path); + + $sql = 'DELETE FROM phpbb_config'; + $result = $db->sql_query($sql); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql, 300); + + $this->assertEquals($expected, $db->sql_fetchrow($result)); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql); + + $no_cache_result = $db->sql_fetchrow($result); + $this->assertSame(false, $no_cache_result); + + $db->sql_close(); + } +} diff --git a/tests/cache/null_driver_test.php b/tests/cache/null_driver_test.php new file mode 100644 index 0000000000..7bf72931a0 --- /dev/null +++ b/tests/cache/null_driver_test.php @@ -0,0 +1,45 @@ +createXMLDataSet(dirname(__FILE__) . '/fixtures/config.xml'); + } + + public function test_null_cache_sql() + { + $driver = new phpbb_cache_driver_null; + + global $db, $cache; + $db = $this->new_dbal(); + $cache = new phpbb_cache_service($driver); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql, 300); + $first_result = $db->sql_fetchrow($result); + $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); + $this->assertEquals($expected, $first_result); + + $sql = 'DELETE FROM phpbb_config'; + $result = $db->sql_query($sql); + + // As null cache driver does not actually cache, + // this should return no results + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql, 300); + + $this->assertSame(false, $db->sql_fetchrow($result)); + + $db->sql_close(); + } +} diff --git a/tests/cache/redis_driver_test.php b/tests/cache/redis_driver_test.php new file mode 100644 index 0000000000..be8b518dca --- /dev/null +++ b/tests/cache/redis_driver_test.php @@ -0,0 +1,89 @@ +createXMLDataSet(dirname(__FILE__) . '/fixtures/config.xml'); + } + + static public function setUpBeforeClass() + { + if (!extension_loaded('redis')) + { + self::markTestSkipped('redis extension is not loaded'); + } + + $config = phpbb_test_case_helpers::get_test_config(); + if (isset($config['redis_host']) || isset($config['redis_port'])) + { + $host = isset($config['redis_host']) ? $config['redis_host'] : 'localhost'; + $port = isset($config['redis_port']) ? $config['redis_port'] : 6379; + self::$config = array('host' => $host, 'port' => $port); + } + else + { + $this->markTestSkipped('Test redis host/port is not specified'); + } + } + + protected function setUp() + { + parent::setUp(); + + $this->driver = new phpbb_cache_driver_redis(self::$config['host'], self::$config['port']); + $this->driver->purge(); + } + + public function test_cache_sql_redis() + { + global $db, $cache; + $db = $this->new_dbal(); + $cache = new phpbb_cache_service($this->driver); + + $redis = new Redis(); + $ok = $redis->connect(self::$config['host'], self::$config['port']); + $this->assertTrue($ok); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + + $key = $this->driver->key_prefix . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)); + $this->assertFalse($redis->exists($key)); + + $result = $db->sql_query($sql, 300); + $first_result = $db->sql_fetchrow($result); + $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); + $this->assertEquals($expected, $first_result); + + $this->assertTrue($redis->exists($key)); + + $sql = 'DELETE FROM phpbb_config'; + $result = $db->sql_query($sql); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql, 300); + + $this->assertEquals($expected, $db->sql_fetchrow($result)); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql); + + $no_cache_result = $db->sql_fetchrow($result); + $this->assertSame(false, $no_cache_result); + + $db->sql_close(); + } +} From 829b75e5c8d7b053f3988db89f6f9505102c92b3 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 15:06:32 -0500 Subject: [PATCH 1088/1142] [ticket/9983] Create driver in setup in null driver test. PHPBB3-9983 --- tests/cache/null_driver_test.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/cache/null_driver_test.php b/tests/cache/null_driver_test.php index 7bf72931a0..45e53edaa1 100644 --- a/tests/cache/null_driver_test.php +++ b/tests/cache/null_driver_test.php @@ -9,18 +9,25 @@ class phpbb_cache_null_driver_test extends phpbb_database_test_case { + protected $driver; + public function getDataSet() { return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/config.xml'); } + protected function setUp() + { + parent::setUp(); + + $this->driver = new phpbb_cache_driver_null; + } + public function test_null_cache_sql() { - $driver = new phpbb_cache_driver_null; - global $db, $cache; $db = $this->new_dbal(); - $cache = new phpbb_cache_service($driver); + $cache = new phpbb_cache_service($this->driver); $sql = "SELECT * FROM phpbb_config WHERE config_name = 'foo'"; From c3d1408c52dabcafa314fcc13d19f0c537d3a5df Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 18:49:27 -0500 Subject: [PATCH 1089/1142] [ticket/9983] get/put cache test moved to a base class. PHPBB3-9983 --- tests/cache/common_test_case.php | 24 ++++++++++++++++++++++++ tests/cache/file_driver_test.php | 16 +++------------- tests/cache/null_driver_test.php | 10 ++++++++++ tests/cache/redis_driver_test.php | 4 +++- 4 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 tests/cache/common_test_case.php diff --git a/tests/cache/common_test_case.php b/tests/cache/common_test_case.php new file mode 100644 index 0000000000..059ce25133 --- /dev/null +++ b/tests/cache/common_test_case.php @@ -0,0 +1,24 @@ +assertSame(false, $this->driver->get('test_key')); + + $this->driver->put('test_key', 'test_value'); + + $this->assertEquals( + 'test_value', + $this->driver->get('test_key'), + 'File ACM put and get' + ); + } +} diff --git a/tests/cache/file_driver_test.php b/tests/cache/file_driver_test.php index 436bd2f6bc..67408cc6d9 100644 --- a/tests/cache/file_driver_test.php +++ b/tests/cache/file_driver_test.php @@ -7,7 +7,9 @@ * */ -class phpbb_cache_file_driver_test extends phpbb_database_test_case +require_once dirname(__FILE__) . '/common_test_case.php'; + +class phpbb_cache_file_driver_test extends phpbb_cache_common_test_case { private $cache_dir; protected $driver; @@ -65,18 +67,6 @@ class phpbb_cache_file_driver_test extends phpbb_database_test_case rmdir($this->cache_dir); } - public function test_cache_driver_file() - { - $this->driver->put('test_key', 'test_value'); - $this->driver->save(); - - $this->assertEquals( - 'test_value', - $this->driver->get('test_key'), - 'File ACM put and get' - ); - } - public function test_cache_sql_file() { global $db, $cache; diff --git a/tests/cache/null_driver_test.php b/tests/cache/null_driver_test.php index 45e53edaa1..efa56762bf 100644 --- a/tests/cache/null_driver_test.php +++ b/tests/cache/null_driver_test.php @@ -23,6 +23,16 @@ class phpbb_cache_null_driver_test extends phpbb_database_test_case $this->driver = new phpbb_cache_driver_null; } + public function test_get_put() + { + $this->assertSame(false, $this->driver->get('key')); + + $this->driver->put('key', 'value'); + + // null driver does not cache + $this->assertSame(false, $this->driver->get('key')); + } + public function test_null_cache_sql() { global $db, $cache; diff --git a/tests/cache/redis_driver_test.php b/tests/cache/redis_driver_test.php index be8b518dca..bf8e49eb65 100644 --- a/tests/cache/redis_driver_test.php +++ b/tests/cache/redis_driver_test.php @@ -7,7 +7,9 @@ * */ -class phpbb_cache_redis_driver_test extends phpbb_database_test_case +require_once dirname(__FILE__) . '/common_test_case.php'; + +class phpbb_cache_redis_driver_test extends phpbb_cache_common_test_case { protected static $config; protected $driver; From 90bd7858fdf418c45bac1b30a4dc634cd23f5247 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 18:52:04 -0500 Subject: [PATCH 1090/1142] [ticket/9983] Rename test methods. PHPBB3-9983 --- tests/cache/file_driver_test.php | 2 +- tests/cache/null_driver_test.php | 2 +- tests/cache/redis_driver_test.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cache/file_driver_test.php b/tests/cache/file_driver_test.php index 67408cc6d9..2353940277 100644 --- a/tests/cache/file_driver_test.php +++ b/tests/cache/file_driver_test.php @@ -67,7 +67,7 @@ class phpbb_cache_file_driver_test extends phpbb_cache_common_test_case rmdir($this->cache_dir); } - public function test_cache_sql_file() + public function test_cache_sql() { global $db, $cache; $db = $this->new_dbal(); diff --git a/tests/cache/null_driver_test.php b/tests/cache/null_driver_test.php index efa56762bf..a9b40c5f40 100644 --- a/tests/cache/null_driver_test.php +++ b/tests/cache/null_driver_test.php @@ -33,7 +33,7 @@ class phpbb_cache_null_driver_test extends phpbb_database_test_case $this->assertSame(false, $this->driver->get('key')); } - public function test_null_cache_sql() + public function test_cache_sql() { global $db, $cache; $db = $this->new_dbal(); diff --git a/tests/cache/redis_driver_test.php b/tests/cache/redis_driver_test.php index bf8e49eb65..cd24e33baf 100644 --- a/tests/cache/redis_driver_test.php +++ b/tests/cache/redis_driver_test.php @@ -47,7 +47,7 @@ class phpbb_cache_redis_driver_test extends phpbb_cache_common_test_case $this->driver->purge(); } - public function test_cache_sql_redis() + public function test_cache_sql() { global $db, $cache; $db = $this->new_dbal(); From d9e636fab0b1c2de36e38e7bdd3498711108a0e6 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 18:53:13 -0500 Subject: [PATCH 1091/1142] [ticket/9983] Add a purge test. PHPBB3-9983 --- tests/cache/common_test_case.php | 15 +++++++++++++++ tests/cache/null_driver_test.php | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/tests/cache/common_test_case.php b/tests/cache/common_test_case.php index 059ce25133..c44dbf5250 100644 --- a/tests/cache/common_test_case.php +++ b/tests/cache/common_test_case.php @@ -21,4 +21,19 @@ abstract class phpbb_cache_common_test_case extends phpbb_database_test_case 'File ACM put and get' ); } + + public function test_purge() + { + $this->driver->put('test_key', 'test_value'); + + $this->assertEquals( + 'test_value', + $this->driver->get('test_key'), + 'File ACM put and get' + ); + + $this->driver->purge(); + + $this->assertSame(false, $this->driver->get('test_key')); + } } diff --git a/tests/cache/null_driver_test.php b/tests/cache/null_driver_test.php index a9b40c5f40..1e8b254294 100644 --- a/tests/cache/null_driver_test.php +++ b/tests/cache/null_driver_test.php @@ -33,6 +33,12 @@ class phpbb_cache_null_driver_test extends phpbb_database_test_case $this->assertSame(false, $this->driver->get('key')); } + public function test_purge() + { + // does nothing + $this->driver->purge(); + } + public function test_cache_sql() { global $db, $cache; From 8595b1f56018d0bfac0b83ac1511175c88a8b9cb Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 18:57:09 -0500 Subject: [PATCH 1092/1142] [ticket/9983] Exercise exists also. PHPBB3-9983 --- tests/cache/common_test_case.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/cache/common_test_case.php b/tests/cache/common_test_case.php index c44dbf5250..5c387d98a1 100644 --- a/tests/cache/common_test_case.php +++ b/tests/cache/common_test_case.php @@ -9,12 +9,14 @@ abstract class phpbb_cache_common_test_case extends phpbb_database_test_case { - public function test_get_put() + public function test_get_put_exists() { + $this->assertFalse($this->driver->_exists('test_key')); $this->assertSame(false, $this->driver->get('test_key')); $this->driver->put('test_key', 'test_value'); + $this->assertTrue($this->driver->_exists('test_key')); $this->assertEquals( 'test_value', $this->driver->get('test_key'), From dd36b128e80112ba47ae51dca47702209e8aac18 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 18:59:28 -0500 Subject: [PATCH 1093/1142] [ticket/9983] Add a test for destroy. PHPBB3-9983 --- tests/cache/common_test_case.php | 23 +++++++++++++++++++++++ tests/cache/null_driver_test.php | 6 ++++++ 2 files changed, 29 insertions(+) diff --git a/tests/cache/common_test_case.php b/tests/cache/common_test_case.php index 5c387d98a1..45cf80e424 100644 --- a/tests/cache/common_test_case.php +++ b/tests/cache/common_test_case.php @@ -38,4 +38,27 @@ abstract class phpbb_cache_common_test_case extends phpbb_database_test_case $this->assertSame(false, $this->driver->get('test_key')); } + + public function test_destroy() + { + $this->driver->put('first_key', 'first_value'); + $this->driver->put('second_key', 'second_value'); + + $this->assertEquals( + 'first_value', + $this->driver->get('first_key') + ); + $this->assertEquals( + 'second_value', + $this->driver->get('second_key') + ); + + $this->driver->destroy('first_key'); + + $this->assertFalse($this->driver->_exists('first_key')); + $this->assertEquals( + 'second_value', + $this->driver->get('second_key') + ); + } } diff --git a/tests/cache/null_driver_test.php b/tests/cache/null_driver_test.php index 1e8b254294..86553d4dc5 100644 --- a/tests/cache/null_driver_test.php +++ b/tests/cache/null_driver_test.php @@ -39,6 +39,12 @@ class phpbb_cache_null_driver_test extends phpbb_database_test_case $this->driver->purge(); } + public function test_destroy() + { + // does nothing + $this->driver->destroy('foo'); + } + public function test_cache_sql() { global $db, $cache; From 720ef233b122d00ef9d2128c9a0a518ff017b0d7 Mon Sep 17 00:00:00 2001 From: Patrick Webster Date: Sat, 1 Dec 2012 22:34:03 -0600 Subject: [PATCH 1094/1142] [ticket/11219] Only update sequences that are affected by a fixture PHPBB3-11219 --- .../phpbb_database_test_case.php | 18 +++-- ...phpbb_database_test_connection_manager.php | 73 +++++++++++-------- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/tests/test_framework/phpbb_database_test_case.php b/tests/test_framework/phpbb_database_test_case.php index 0916679108..429bb92bf1 100644 --- a/tests/test_framework/phpbb_database_test_case.php +++ b/tests/test_framework/phpbb_database_test_case.php @@ -13,6 +13,8 @@ abstract class phpbb_database_test_case extends PHPUnit_Extensions_Database_Test protected $test_case_helpers; + protected $fixture_xml_data; + public function __construct($name = NULL, array $data = array(), $dataName = '') { parent::__construct($name, $data, $dataName); @@ -32,10 +34,14 @@ abstract class phpbb_database_test_case extends PHPUnit_Extensions_Database_Test { parent::setUp(); - $config = $this->get_database_config(); - $manager = $this->create_connection_manager($config); - $manager->connect(); - $manager->post_setup_synchronisation(); + // Resynchronise tables if a fixture was loaded + if (isset($this->fixture_xml_data)) + { + $config = $this->get_database_config(); + $manager = $this->create_connection_manager($config); + $manager->connect(); + $manager->post_setup_synchronisation($this->fixture_xml_data); + } } public function createXMLDataSet($path) @@ -57,7 +63,9 @@ abstract class phpbb_database_test_case extends PHPUnit_Extensions_Database_Test $path = $meta_data['uri']; } - return parent::createXMLDataSet($path); + $this->fixture_xml_data = parent::createXMLDataSet($path); + + return $this->fixture_xml_data; } public function get_test_case_helpers() diff --git a/tests/test_framework/phpbb_database_test_connection_manager.php b/tests/test_framework/phpbb_database_test_connection_manager.php index e79a764e1d..97281a0812 100644 --- a/tests/test_framework/phpbb_database_test_connection_manager.php +++ b/tests/test_framework/phpbb_database_test_connection_manager.php @@ -429,12 +429,19 @@ class phpbb_database_test_connection_manager /** * Performs synchronisations on the database after a fixture has been loaded + * + * @param PHPUnit_Extensions_Database_DataSet_XmlDataSet $tables Tables contained within the loaded fixture + * + * @return null */ - public function post_setup_synchronisation() + public function post_setup_synchronisation($xmlDataSet) { $this->ensure_connected(__METHOD__); $queries = array(); + // Get escaped versions of the table names used in the fixture + $table_names = array_map(array($this->pdo, 'PDO::quote'), $xmlDataSet->getTableNames()); + switch ($this->config['dbms']) { case 'oracle': @@ -445,7 +452,9 @@ class phpbb_database_test_connection_manager JOIN USER_TRIGGER_COLS tc ON (tc.trigger_name = t.trigger_name) JOIN USER_SEQUENCES s ON (s.sequence_name = d.referenced_name) WHERE d.referenced_type = 'SEQUENCE' - AND d.type = 'TRIGGER'"; + AND d.type = 'TRIGGER' + AND t.table_name IN (" . implode(', ', array_map('strtoupper', $table_names)) . ')'; + $result = $this->pdo->query($sql); while ($row = $result->fetch(PDO::FETCH_ASSOC)) @@ -476,41 +485,43 @@ class phpbb_database_test_connection_manager break; case 'postgres': - // First get the sequences - $sequences = array(); - $sql = "SELECT relname FROM pg_class WHERE relkind = 'S'"; + // Get the sequences attached to the tables + $sql = 'SELECT column_name, table_name FROM information_schema.columns + WHERE table_name IN (' . implode(', ', $table_names) . ") + AND strpos(column_default, '_seq''::regclass') > 0"; $result = $this->pdo->query($sql); + + $setval_queries = array(); while ($row = $result->fetch(PDO::FETCH_ASSOC)) { - $sequences[] = $row['relname']; + // Get the columns used in the fixture for this table + $column_names = $xmlDataSet->getTableMetaData($row['table_name'])->getColumns(); + + // Skip sequences that weren't specified in the fixture + if (!in_array($row['column_name'], $column_names)) + { + continue; + } + + // Get the old value if it exists, or use 1 if it doesn't + $sql = "SELECT COALESCE((SELECT MAX({$row['column_name']}) + 1 FROM {$row['table_name']}), 1) AS val"; + $result_max = $this->pdo->query($sql); + $row_max = $result_max->fetch(PDO::FETCH_ASSOC); + + if ($row_max) + { + $seq_name = $this->pdo->quote($row['table_name'] . '_seq'); + $max_val = (int) $row_max['val']; + + // The last parameter is false so that the system doesn't increment it again + $setval_queries[] = "SETVAL($seq_name, $max_val, false)"; + } } - // Now get the name of the column using it - foreach ($sequences as $sequence) + // Combine all of the SETVALs into one query + if (sizeof($setval_queries)) { - $table = str_replace('_seq', '', $sequence); - $sql = "SELECT column_name FROM information_schema.columns - WHERE table_name = '$table' - AND column_default = 'nextval(''$sequence''::regclass)'"; - $result = $this->pdo->query($sql); - $row = $result->fetch(PDO::FETCH_ASSOC); - - // Finally, set the new sequence value - if ($row) - { - $column = $row['column_name']; - - // Get the old value if it exists, or use 1 if it doesn't - $sql = "SELECT COALESCE((SELECT MAX({$column}) + 1 FROM {$table}), 1) AS val"; - $result = $this->pdo->query($sql); - $row = $result->fetch(PDO::FETCH_ASSOC); - - if ($row) - { - // The last parameter is false so that the system doesn't increment it again - $queries[] = "SELECT SETVAL('{$sequence}', {$row['val']}, false)"; - } - } + $queries[] = 'SELECT ' . implode(', ', $setval_queries); } break; } From dacacbbd52dc22f3344f313301cf6b4fb0f77233 Mon Sep 17 00:00:00 2001 From: Hari Sankar R Date: Sun, 15 Apr 2012 13:08:44 +0530 Subject: [PATCH 1095/1142] [ticket/10771] Using Remember Me instead of autologin or persistent keys Changed language variable LOG_ME_IN's value to "Remember me" PHPBB3-10771 --- phpBB/language/en/common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 4300ef431a..71f1beb66a 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -343,7 +343,7 @@ $lang = array_merge($lang, array( 'LOGIN_EXPLAIN_VIEWONLINE' => 'In order to view the online list you have to be registered and logged in.', 'LOGOUT' => 'Logout', 'LOGOUT_USER' => 'Logout [ %s ]', - 'LOG_ME_IN' => 'Log me on automatically each visit', + 'LOG_ME_IN' => 'Remember me', 'MARK' => 'Mark', 'MARK_ALL' => 'Mark all', From a17c1a29f8ce5781082dcf87a8bde261647062f0 Mon Sep 17 00:00:00 2001 From: Hari Sankar R Date: Wed, 18 Apr 2012 12:13:00 +0530 Subject: [PATCH 1096/1142] [ticket/10771] changed value in help_faq.php PHPBB3-10771 --- phpBB/language/en/help_faq.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/language/en/help_faq.php b/phpBB/language/en/help_faq.php index 5c99f81c06..9500943b88 100644 --- a/phpBB/language/en/help_faq.php +++ b/phpBB/language/en/help_faq.php @@ -43,7 +43,7 @@ $help = array( ), array( 0 => 'Why do I get logged off automatically?', - 1 => 'If you do not check the Log me in automatically box when you login, the board will only keep you logged in for a preset time. This prevents misuse of your account by anyone else. To stay logged in, check the box during login. This is not recommended if you access the board from a shared computer, e.g. library, internet cafe, university computer lab, etc. If you do not see this checkbox, it means the board administrator has disabled this feature.' + 1 => 'If you do not check the Remember me box when you login, the board will only keep you logged in for a preset time. This prevents misuse of your account by anyone else. To stay logged in, check the box during login. This is not recommended if you access the board from a shared computer, e.g. library, internet cafe, university computer lab, etc. If you do not see this checkbox, it means the board administrator has disabled this feature.' ), array( 0 => 'How do I prevent my username appearing in the online user listings?', From a97401a180657b8b9526c5a0af055d46cf3e8487 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Sun, 11 Nov 2012 00:03:22 +0100 Subject: [PATCH 1097/1142] [ticket/10771] use remember me in acp PHPBB3-10771 --- phpBB/language/en/acp/board.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index a4380486cc..4adcbd689e 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -449,10 +449,10 @@ $lang = array_merge($lang, array( 'ACP_SECURITY_SETTINGS_EXPLAIN' => 'Here you are able to define session and login related settings.', 'ALL' => 'All', - 'ALLOW_AUTOLOGIN' => 'Allow persistent logins', - 'ALLOW_AUTOLOGIN_EXPLAIN' => 'Determines whether users can autologin when they visit the board.', - 'AUTOLOGIN_LENGTH' => 'Persistent login key expiration length (in days)', - 'AUTOLOGIN_LENGTH_EXPLAIN' => 'Number of days after which persistent login keys are removed or zero to disable.', + 'ALLOW_AUTOLOGIN' => 'Allow remember me logins', + 'ALLOW_AUTOLOGIN_EXPLAIN' => 'Determines whether users can select remember me option when they visit the board.', + 'AUTOLOGIN_LENGTH' => 'Remember me login key expiration length (in days)', + 'AUTOLOGIN_LENGTH_EXPLAIN' => 'Number of days after which remember me login keys are removed or zero to disable.', 'BROWSER_VALID' => 'Validate browser', 'BROWSER_VALID_EXPLAIN' => 'Enables browser validation for each session improving security.', 'CHECK_DNSBL' => 'Check IP against DNS Blackhole List', From 619e15378dd6839b26ceb7a698b8d07fa5ebbec9 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Sun, 11 Nov 2012 00:03:45 +0100 Subject: [PATCH 1098/1142] [ticket/10771] use remember me in ucp PHPBB3-10771 --- phpBB/language/en/ucp.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 705b07b170..0ae58392d5 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -87,7 +87,7 @@ $lang = array_merge($lang, array( 'ATTACHMENTS_EXPLAIN' => 'This is a list of attachments you have made in posts to this board.', 'ATTACHMENTS_DELETED' => 'Attachments successfully deleted.', 'ATTACHMENT_DELETED' => 'Attachment successfully deleted.', - 'AUTOLOGIN_SESSION_KEYS_DELETED'=> 'The selected persistent login keys were successfully deleted.', + 'AUTOLOGIN_SESSION_KEYS_DELETED'=> 'The selected remember me login keys were successfully deleted.', 'AVATAR_CATEGORY' => 'Category', 'AVATAR_EXPLAIN' => 'Maximum dimensions; width: %1$s, height: %2$s, file size: %3$.2f KiB.', 'AVATAR_FEATURES_DISABLED' => 'The avatar functionality is currently disabled.', @@ -380,8 +380,8 @@ $lang = array_merge($lang, array( 'PREFERENCES_UPDATED' => 'Your preferences have been updated.', 'PROFILE_INFO_NOTICE' => 'Please note that this information may be viewable to other members. Be careful when including any personal details. Any fields marked with a * must be completed.', 'PROFILE_UPDATED' => 'Your profile has been updated.', - 'PROFILE_AUTOLOGIN_KEYS' => 'The persistent login keys automatically log you in when you visit the board. If you logout, the persistent login key is deleted only on the computer you are using to logout. Here you can see persistent login keys created on other computers you used to access this site.', - 'PROFILE_NO_AUTOLOGIN_KEYS' => 'There are no saved persistent login keys.', + 'PROFILE_AUTOLOGIN_KEYS' => 'The remember me login keys automatically log you in when you visit the board. If you logout, the remember me login key is deleted only on the computer you are using to logout. Here you can see remember login keys created on other computers you used to access this site.', + 'PROFILE_NO_AUTOLOGIN_KEYS' => 'There are no saved remember me login keys.', 'RECIPIENT' => 'Recipient', 'RECIPIENTS' => 'Recipients', @@ -476,7 +476,7 @@ $lang = array_merge($lang, array( 'UCP_PROFILE_PROFILE_INFO' => 'Edit profile', 'UCP_PROFILE_REG_DETAILS' => 'Edit account settings', 'UCP_PROFILE_SIGNATURE' => 'Edit signature', - 'UCP_PROFILE_AUTOLOGIN_KEYS'=> 'Edit persistent login keys', + 'UCP_PROFILE_AUTOLOGIN_KEYS'=> 'Edit remember me login keys', 'UCP_USERGROUPS' => 'Usergroups', 'UCP_USERGROUPS_MEMBER' => 'Edit memberships', From 37f2841af2d1027b92953ee2c08da44481d0a06e Mon Sep 17 00:00:00 2001 From: Dhruv Date: Sun, 2 Dec 2012 15:20:43 +0530 Subject: [PATCH 1099/1142] [ticket/10771] fix remember me language PHPBB3-10771 --- phpBB/language/en/acp/board.php | 8 ++++---- phpBB/language/en/ucp.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 4adcbd689e..1b99c87938 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -449,10 +449,10 @@ $lang = array_merge($lang, array( 'ACP_SECURITY_SETTINGS_EXPLAIN' => 'Here you are able to define session and login related settings.', 'ALL' => 'All', - 'ALLOW_AUTOLOGIN' => 'Allow remember me logins', - 'ALLOW_AUTOLOGIN_EXPLAIN' => 'Determines whether users can select remember me option when they visit the board.', - 'AUTOLOGIN_LENGTH' => 'Remember me login key expiration length (in days)', - 'AUTOLOGIN_LENGTH_EXPLAIN' => 'Number of days after which remember me login keys are removed or zero to disable.', + 'ALLOW_AUTOLOGIN' => 'Allow "Remember Me" logins', + 'ALLOW_AUTOLOGIN_EXPLAIN' => 'Determines whether users are given "Remember Me" option when they visit the board.', + 'AUTOLOGIN_LENGTH' => '"Remember Me" login key expiration length (in days)', + 'AUTOLOGIN_LENGTH_EXPLAIN' => 'Number of days after which "Remember Me" login keys are removed or zero to disable.', 'BROWSER_VALID' => 'Validate browser', 'BROWSER_VALID_EXPLAIN' => 'Enables browser validation for each session improving security.', 'CHECK_DNSBL' => 'Check IP against DNS Blackhole List', diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 0ae58392d5..b919699ea0 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -87,7 +87,7 @@ $lang = array_merge($lang, array( 'ATTACHMENTS_EXPLAIN' => 'This is a list of attachments you have made in posts to this board.', 'ATTACHMENTS_DELETED' => 'Attachments successfully deleted.', 'ATTACHMENT_DELETED' => 'Attachment successfully deleted.', - 'AUTOLOGIN_SESSION_KEYS_DELETED'=> 'The selected remember me login keys were successfully deleted.', + 'AUTOLOGIN_SESSION_KEYS_DELETED'=> 'The selected "Remember Me" login keys were successfully deleted.', 'AVATAR_CATEGORY' => 'Category', 'AVATAR_EXPLAIN' => 'Maximum dimensions; width: %1$s, height: %2$s, file size: %3$.2f KiB.', 'AVATAR_FEATURES_DISABLED' => 'The avatar functionality is currently disabled.', @@ -380,8 +380,8 @@ $lang = array_merge($lang, array( 'PREFERENCES_UPDATED' => 'Your preferences have been updated.', 'PROFILE_INFO_NOTICE' => 'Please note that this information may be viewable to other members. Be careful when including any personal details. Any fields marked with a * must be completed.', 'PROFILE_UPDATED' => 'Your profile has been updated.', - 'PROFILE_AUTOLOGIN_KEYS' => 'The remember me login keys automatically log you in when you visit the board. If you logout, the remember me login key is deleted only on the computer you are using to logout. Here you can see remember login keys created on other computers you used to access this site.', - 'PROFILE_NO_AUTOLOGIN_KEYS' => 'There are no saved remember me login keys.', + 'PROFILE_AUTOLOGIN_KEYS' => 'The "Remember Me" login keys automatically log you in when you visit the board. If you logout, the remember me login key is deleted only on the computer you are using to logout. Here you can see remember login keys created on other computers you used to access this site.', + 'PROFILE_NO_AUTOLOGIN_KEYS' => 'There are no saved "Remember Me" login keys.', 'RECIPIENT' => 'Recipient', 'RECIPIENTS' => 'Recipients', @@ -476,7 +476,7 @@ $lang = array_merge($lang, array( 'UCP_PROFILE_PROFILE_INFO' => 'Edit profile', 'UCP_PROFILE_REG_DETAILS' => 'Edit account settings', 'UCP_PROFILE_SIGNATURE' => 'Edit signature', - 'UCP_PROFILE_AUTOLOGIN_KEYS'=> 'Edit remember me login keys', + 'UCP_PROFILE_AUTOLOGIN_KEYS'=> 'Edit "Remember Me" login keys', 'UCP_USERGROUPS' => 'Usergroups', 'UCP_USERGROUPS_MEMBER' => 'Edit memberships', From 9d5fc4ae33ac5a5ea2dc85fd4157ce56653c4a16 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 2 Dec 2012 13:16:11 -0500 Subject: [PATCH 1100/1142] [ticket/11238] Set goutte to 0.1.0 in develop. PHPBB3-11238 --- phpBB/composer.json | 2 +- phpBB/composer.lock | 170 +++++++++++++++++--------------------------- 2 files changed, 68 insertions(+), 104 deletions(-) diff --git a/phpBB/composer.json b/phpBB/composer.json index 5a03e68f73..d2536a73cf 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -9,6 +9,6 @@ "symfony/yaml": "2.1.*" }, "require-dev": { - "fabpot/goutte": "1.0.x-dev" + "fabpot/goutte": "v0.1.0" } } diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 62ece6d505..e96c15fe8b 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -1,5 +1,5 @@ { - "hash": "efb4768ba71d7cd2c84baa0610d84067", + "hash": "c1a76530df6b9daa16b8033d61b76503", "packages": [ { "name": "symfony/config", @@ -381,29 +381,29 @@ "packages-dev": [ { "name": "fabpot/goutte", - "version": "dev-master", + "version": "v0.1.0", "source": { "type": "git", "url": "https://github.com/fabpot/Goutte", - "reference": "f2940f9c7c1f409159f5e9f512e575946c5cff48" + "reference": "v0.1.0" }, "dist": { "type": "zip", - "url": "https://github.com/fabpot/Goutte/archive/f2940f9c7c1f409159f5e9f512e575946c5cff48.zip", - "reference": "f2940f9c7c1f409159f5e9f512e575946c5cff48", + "url": "https://github.com/fabpot/Goutte/archive/v0.1.0.zip", + "reference": "v0.1.0", "shasum": "" }, "require": { "php": ">=5.3.0", + "ext-curl": "*", "symfony/browser-kit": "2.1.*", "symfony/css-selector": "2.1.*", "symfony/dom-crawler": "2.1.*", "symfony/finder": "2.1.*", "symfony/process": "2.1.*", - "ext-curl": "*", - "guzzle/http": "2.8.*" + "guzzle/guzzle": "3.0.*" }, - "time": "1351086217", + "time": "2012-12-02 13:44:35", "type": "application", "extra": { "branch-alias": { @@ -432,126 +432,90 @@ ] }, { - "name": "guzzle/common", - "version": "v2.8.8", - "target-dir": "Guzzle/Common", + "name": "guzzle/guzzle", + "version": "v3.0.5", "source": { "type": "git", - "url": "git://github.com/guzzle/common.git", - "reference": "v2.8.8" + "url": "https://github.com/guzzle/guzzle", + "reference": "v3.0.5" }, "dist": { "type": "zip", - "url": "https://github.com/guzzle/common/zipball/v2.8.8", - "reference": "v2.8.8", + "url": "https://github.com/guzzle/guzzle/archive/v3.0.5.zip", + "reference": "v3.0.5", "shasum": "" }, "require": { "php": ">=5.3.2", + "ext-curl": "*", "symfony/event-dispatcher": "2.1.*" }, - "time": "2012-10-15 17:42:47", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-0": { - "Guzzle\\Common": "" - } - }, - "license": [ - "MIT" - ], - "description": "Common libraries used by Guzzle", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "log", - "event", - "cache", - "validation", - "Socket", - "common", - "batch", - "inflection" - ] - }, - { - "name": "guzzle/http", - "version": "v2.8.8", - "target-dir": "Guzzle/Http", - "source": { - "type": "git", - "url": "git://github.com/guzzle/http.git", - "reference": "v2.8.8" - }, - "dist": { - "type": "zip", - "url": "https://github.com/guzzle/http/zipball/v2.8.8", - "reference": "v2.8.8", - "shasum": "" - }, - "require": { - "php": ">=5.3.2", + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", "guzzle/common": "self.version", - "guzzle/parser": "self.version" + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" }, - "time": "2012-10-15 17:42:47", + "require-dev": { + "doctrine/common": "*", + "symfony/class-loader": "*", + "monolog/monolog": "1.*", + "zendframework/zend-cache": "2.0.*", + "zendframework/zend-log": "2.0.*", + "zend/zend-log1": "1.12", + "zend/zend-cache1": "1.12", + "phpunit/phpunit": "3.7.*" + }, + "time": "2012-11-19 00:15:33", "type": "library", "installation-source": "dist", "autoload": { "psr-0": { - "Guzzle\\Http": "" + "Guzzle\\Tests": "tests/", + "Guzzle": "src/" } }, "license": [ "MIT" ], - "description": "HTTP libraries used by Guzzle", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", "homepage": "http://guzzlephp.org/", "keywords": [ + "framework", "curl", "http", + "rest", "http client", "client", - "Guzzle" - ] - }, - { - "name": "guzzle/parser", - "version": "v2.8.8", - "target-dir": "Guzzle/Parser", - "source": { - "type": "git", - "url": "git://github.com/guzzle/parser.git", - "reference": "v2.8.8" - }, - "dist": { - "type": "zip", - "url": "https://github.com/guzzle/parser/zipball/v2.8.8", - "reference": "v2.8.8", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "time": "2012-09-20 13:28:06", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-0": { - "Guzzle\\Parser": "" - } - }, - "license": [ - "MIT" - ], - "description": "Interchangeable parsers used by Guzzle", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "http", - "url", - "message", - "cookie", - "URI Template" + "web service" ] }, { @@ -808,7 +772,7 @@ ], "minimum-stability": "beta", - "stability-flags": { - "fabpot/goutte": 20 - } + "stability-flags": [ + + ] } From 6f7e39996c926b0f0080ccdd594fd465c311cb79 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 2 Dec 2012 23:44:49 -0500 Subject: [PATCH 1101/1142] [ticket/11238] Set goutte version to 0.1.0. PHPBB3-11238 --- phpBB/composer.json | 2 +- phpBB/composer.lock | 463 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 433 insertions(+), 32 deletions(-) diff --git a/phpBB/composer.json b/phpBB/composer.json index c2811ad1d7..8d5dd1d52e 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -1,6 +1,6 @@ { "minimum-stability": "beta", "require-dev": { - "fabpot/goutte": "1.0.x-dev" + "fabpot/goutte": "v0.1.0" } } diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 9f2195e70a..a78fff9a14 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -1,55 +1,456 @@ { - "hash": "a5d02c59e3a91c84c1a96aca0f1ae81a", + "hash": "23352b29002e6d22658e314a8e737f71", "packages": [ - + { + "name": "symfony/event-dispatcher", + "version": "v2.1.4", + "target-dir": "Symfony/Component/EventDispatcher", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher", + "reference": "v2.1.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/symfony/EventDispatcher/archive/v2.1.4.zip", + "reference": "v2.1.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/dependency-injection": "2.1.*" + }, + "suggest": { + "symfony/dependency-injection": "2.1.*", + "symfony/http-kernel": "2.1.*" + }, + "time": "2012-11-08 09:51:48", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "http://symfony.com" + } ], "packages-dev": [ { - "package": "fabpot/goutte", - "version": "dev-master", - "alias-pretty-version": "1.0.x-dev", - "alias-version": "1.0.9999999.9999999-dev" + "name": "fabpot/goutte", + "version": "v0.1.0", + "source": { + "type": "git", + "url": "https://github.com/fabpot/Goutte", + "reference": "v0.1.0" + }, + "dist": { + "type": "zip", + "url": "https://github.com/fabpot/Goutte/archive/v0.1.0.zip", + "reference": "v0.1.0", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "ext-curl": "*", + "symfony/browser-kit": "2.1.*", + "symfony/css-selector": "2.1.*", + "symfony/dom-crawler": "2.1.*", + "symfony/finder": "2.1.*", + "symfony/process": "2.1.*", + "guzzle/guzzle": "3.0.*" + }, + "time": "2012-12-02 13:44:35", + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "source", + "autoload": { + "psr-0": { + "Goutte": "." + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A simple PHP Web Scraper", + "homepage": "https://github.com/fabpot/Goutte", + "keywords": [ + "scraper" + ] }, { - "package": "fabpot/goutte", - "version": "dev-master", - "source-reference": "c2ea8d9a6682d14482e57ede2371001b8a5238d2", - "commit-date": "1340264258" + "name": "guzzle/guzzle", + "version": "v3.0.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle", + "reference": "v3.0.5" + }, + "dist": { + "type": "zip", + "url": "https://github.com/guzzle/guzzle/archive/v3.0.5.zip", + "reference": "v3.0.5", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "ext-curl": "*", + "symfony/event-dispatcher": "2.1.*" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/common": "*", + "symfony/class-loader": "*", + "monolog/monolog": "1.*", + "zendframework/zend-cache": "2.0.*", + "zendframework/zend-log": "2.0.*", + "zend/zend-log1": "1.12", + "zend/zend-cache1": "1.12", + "phpunit/phpunit": "3.7.*" + }, + "time": "2012-11-19 00:15:33", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Guzzle\\Tests": "tests/", + "Guzzle": "src/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "framework", + "curl", + "http", + "rest", + "http client", + "client", + "web service" + ] }, { - "package": "guzzle/guzzle", - "version": "v2.6.6" + "name": "symfony/browser-kit", + "version": "v2.1.4", + "target-dir": "Symfony/Component/BrowserKit", + "source": { + "type": "git", + "url": "https://github.com/symfony/BrowserKit", + "reference": "v2.1.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/symfony/BrowserKit/archive/v2.1.4.zip", + "reference": "v2.1.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/dom-crawler": "2.1.*" + }, + "require-dev": { + "symfony/process": "2.1.*", + "symfony/css-selector": "2.1.*" + }, + "suggest": { + "symfony/process": "2.1.*" + }, + "time": "2012-11-08 09:51:48", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\BrowserKit": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "http://symfony.com" }, { - "package": "symfony/browser-kit", - "version": "v2.1.0-BETA3" + "name": "symfony/css-selector", + "version": "v2.1.4", + "target-dir": "Symfony/Component/CssSelector", + "source": { + "type": "git", + "url": "https://github.com/symfony/CssSelector", + "reference": "v2.1.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/symfony/CssSelector/archive/v2.1.4.zip", + "reference": "v2.1.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2012-11-08 09:51:48", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\CssSelector": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "http://symfony.com" }, { - "package": "symfony/css-selector", - "version": "v2.1.0-BETA3" + "name": "symfony/dom-crawler", + "version": "v2.1.4", + "target-dir": "Symfony/Component/DomCrawler", + "source": { + "type": "git", + "url": "https://github.com/symfony/DomCrawler", + "reference": "v2.1.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/symfony/DomCrawler/archive/v2.1.4.zip", + "reference": "v2.1.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/css-selector": "2.1.*" + }, + "suggest": { + "symfony/css-selector": "2.1.*" + }, + "time": "2012-11-29 10:32:18", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\DomCrawler": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "http://symfony.com" }, { - "package": "symfony/dom-crawler", - "version": "v2.1.0-BETA3" + "name": "symfony/finder", + "version": "v2.1.4", + "target-dir": "Symfony/Component/Finder", + "source": { + "type": "git", + "url": "https://github.com/symfony/Finder", + "reference": "v2.1.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/symfony/Finder/archive/v2.1.4.zip", + "reference": "v2.1.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2012-11-08 09:51:48", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Finder": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "http://symfony.com" }, { - "package": "symfony/event-dispatcher", - "version": "v2.1.0-BETA3" - }, - { - "package": "symfony/finder", - "version": "v2.1.0-BETA3" - }, - { - "package": "symfony/process", - "version": "v2.1.0-BETA3" + "name": "symfony/process", + "version": "v2.1.4", + "target-dir": "Symfony/Component/Process", + "source": { + "type": "git", + "url": "https://github.com/symfony/Process", + "reference": "v2.1.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/symfony/Process/archive/v2.1.4.zip", + "reference": "v2.1.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2012-11-19 20:53:52", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Process": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "http://symfony.com" } ], "aliases": [ ], "minimum-stability": "beta", - "stability-flags": { - "fabpot/goutte": 20 - } + "stability-flags": [ + + ] } From df78c616aadf57ebb00988b299b34d39b558fda1 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 2 Dec 2012 23:54:59 -0500 Subject: [PATCH 1102/1142] [ticket/9983] Indeed, it is Date: Sun, 2 Dec 2012 23:55:42 -0500 Subject: [PATCH 1103/1142] [ticket/9983] Empty line by request. PHPBB3-9983 --- phpBB/includes/cache/driver/redis.php | 1 + 1 file changed, 1 insertion(+) diff --git a/phpBB/includes/cache/driver/redis.php b/phpBB/includes/cache/driver/redis.php index ae6f9e04f2..960735b673 100644 --- a/phpBB/includes/cache/driver/redis.php +++ b/phpBB/includes/cache/driver/redis.php @@ -67,6 +67,7 @@ class phpbb_cache_driver_redis extends phpbb_cache_driver_memory { $ok = $this->redis->connect(PHPBB_ACM_REDIS_HOST, PHPBB_ACM_REDIS_PORT); } + if (!$ok) { trigger_error('Could not connect to redis server'); From 7d09b9b4bb58e71f0e7b895ef32059ad08d12416 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 2 Dec 2012 23:57:37 -0500 Subject: [PATCH 1104/1142] [ticket/9983] Add phpbb prefix to global variables. PHPBB3-9983 --- tests/RUNNING_TESTS.txt | 4 ++-- tests/test_framework/phpbb_test_case_helpers.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/RUNNING_TESTS.txt b/tests/RUNNING_TESTS.txt index 395cf1240a..75a6fc94f6 100644 --- a/tests/RUNNING_TESTS.txt +++ b/tests/RUNNING_TESTS.txt @@ -80,8 +80,8 @@ or port must be specified in test configuration. This can be done via test_config.php as follows: Date: Mon, 3 Dec 2012 00:18:52 -0500 Subject: [PATCH 1105/1142] [ticket/9983] Test for apc cache driver. PHPBB3-9983 --- tests/cache/apc_driver_test.php | 79 +++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/cache/apc_driver_test.php diff --git a/tests/cache/apc_driver_test.php b/tests/cache/apc_driver_test.php new file mode 100644 index 0000000000..f6e001f705 --- /dev/null +++ b/tests/cache/apc_driver_test.php @@ -0,0 +1,79 @@ +createXMLDataSet(dirname(__FILE__) . '/fixtures/config.xml'); + } + + static public function setUpBeforeClass() + { + if (!extension_loaded('apc')) + { + self::markTestSkipped('apc extension is not loaded'); + } + } + + protected function setUp() + { + parent::setUp(); + + $this->driver = new phpbb_cache_driver_apc; + $this->driver->purge(); + } + + public function test_cache_sql() + { + global $db, $cache; + $db = $this->new_dbal(); + $cache = new phpbb_cache_service($this->driver); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + + $key = $this->driver->key_prefix . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)); + $this->assertFalse(apc_fetch($key)); + + $result = $db->sql_query($sql, 300); + $first_result = $db->sql_fetchrow($result); + $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); + $this->assertEquals($expected, $first_result); + + $this->assertTrue((bool) apc_fetch($key)); + + $sql = 'DELETE FROM phpbb_config'; + $result = $db->sql_query($sql); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql, 300); + + $this->assertEquals($expected, $db->sql_fetchrow($result)); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql); + + $no_cache_result = $db->sql_fetchrow($result); + $this->assertSame(false, $no_cache_result); + + $db->sql_close(); + } +} From 0b4b7e68a75438368868ede050bc6dd66fc80767 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Mon, 3 Dec 2012 14:17:34 +0100 Subject: [PATCH 1106/1142] [ticket/9983] Skip tests if APC is not enabled for CLI. PHPBB3-9983 --- tests/cache/apc_driver_test.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/cache/apc_driver_test.php b/tests/cache/apc_driver_test.php index f6e001f705..f7cf2119ed 100644 --- a/tests/cache/apc_driver_test.php +++ b/tests/cache/apc_driver_test.php @@ -29,6 +29,12 @@ class phpbb_cache_apc_driver_test extends phpbb_cache_common_test_case { self::markTestSkipped('apc extension is not loaded'); } + + $php_ini = new phpbb_php_ini; + if (PHP_SAPI == 'cli' && !$php_ini->get_bool('apc.enable_cli')) + { + self::markTestSkipped('APC is not enabled for CLI. Set apc.enable_cli=1 in php.ini'); + } } protected function setUp() From 2e851baab90b0c9960eb7427acf41bfb8a848195 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Mon, 3 Dec 2012 14:18:05 +0100 Subject: [PATCH 1107/1142] [ticket/9983] Use APC instead of apc in error messages. PHPBB3-9983 --- tests/cache/apc_driver_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cache/apc_driver_test.php b/tests/cache/apc_driver_test.php index f7cf2119ed..100ba76686 100644 --- a/tests/cache/apc_driver_test.php +++ b/tests/cache/apc_driver_test.php @@ -27,7 +27,7 @@ class phpbb_cache_apc_driver_test extends phpbb_cache_common_test_case { if (!extension_loaded('apc')) { - self::markTestSkipped('apc extension is not loaded'); + self::markTestSkipped('APC extension is not loaded'); } $php_ini = new phpbb_php_ini; From db6b11a3902c27b612d7d6d4696c4cd8cf1f0bdf Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Mon, 3 Dec 2012 14:35:59 +0100 Subject: [PATCH 1108/1142] [ticket/9983] Also check generic APC enable/disable. PHPBB3-9983 --- tests/cache/apc_driver_test.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/cache/apc_driver_test.php b/tests/cache/apc_driver_test.php index 100ba76686..c8b8f82b67 100644 --- a/tests/cache/apc_driver_test.php +++ b/tests/cache/apc_driver_test.php @@ -31,6 +31,12 @@ class phpbb_cache_apc_driver_test extends phpbb_cache_common_test_case } $php_ini = new phpbb_php_ini; + + if (!$php_ini->get_bool('apc.enabled')) + { + self::markTestSkipped('APC is not enabled. Make sure apc.enabled=1 in php.ini'); + } + if (PHP_SAPI == 'cli' && !$php_ini->get_bool('apc.enable_cli')) { self::markTestSkipped('APC is not enabled for CLI. Set apc.enable_cli=1 in php.ini'); From e845e2ed89f75a5c9f14191f833334d84d389faf Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Tue, 4 Dec 2012 00:40:24 +0100 Subject: [PATCH 1109/1142] [ticket/11240] Enable PHPUnit's verbose mode so we get a list of skipped tests. PHPBB3-11240 --- travis/phpunit-mysql-travis.xml | 1 + travis/phpunit-postgres-travis.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/travis/phpunit-mysql-travis.xml b/travis/phpunit-mysql-travis.xml index e54b2bb77b..5366494c8b 100644 --- a/travis/phpunit-mysql-travis.xml +++ b/travis/phpunit-mysql-travis.xml @@ -9,6 +9,7 @@ stopOnFailure="false" syntaxCheck="true" strict="true" + verbose="true" bootstrap="../tests/bootstrap.php"> diff --git a/travis/phpunit-postgres-travis.xml b/travis/phpunit-postgres-travis.xml index 55ba996548..0383edd9d6 100644 --- a/travis/phpunit-postgres-travis.xml +++ b/travis/phpunit-postgres-travis.xml @@ -9,6 +9,7 @@ stopOnFailure="false" syntaxCheck="true" strict="true" + verbose="true" bootstrap="../tests/bootstrap.php"> From d93f582b04d2e6d0738cd6a2ffee739b8c987276 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Mon, 3 Dec 2012 21:47:29 -0500 Subject: [PATCH 1110/1142] [ticket/9983] Make sql cache test into a black box. This makes it non-driver-specific and also makes it possible to make prefix variable private on drivers. PHPBB3-9983 --- tests/cache/apc_driver_test.php | 38 ---------------------------- tests/cache/common_test_case.php | 33 ++++++++++++++++++++++++ tests/cache/file_driver_test.php | 38 ---------------------------- tests/cache/redis_driver_test.php | 42 ------------------------------- 4 files changed, 33 insertions(+), 118 deletions(-) diff --git a/tests/cache/apc_driver_test.php b/tests/cache/apc_driver_test.php index c8b8f82b67..3380762878 100644 --- a/tests/cache/apc_driver_test.php +++ b/tests/cache/apc_driver_test.php @@ -50,42 +50,4 @@ class phpbb_cache_apc_driver_test extends phpbb_cache_common_test_case $this->driver = new phpbb_cache_driver_apc; $this->driver->purge(); } - - public function test_cache_sql() - { - global $db, $cache; - $db = $this->new_dbal(); - $cache = new phpbb_cache_service($this->driver); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - - $key = $this->driver->key_prefix . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)); - $this->assertFalse(apc_fetch($key)); - - $result = $db->sql_query($sql, 300); - $first_result = $db->sql_fetchrow($result); - $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); - $this->assertEquals($expected, $first_result); - - $this->assertTrue((bool) apc_fetch($key)); - - $sql = 'DELETE FROM phpbb_config'; - $result = $db->sql_query($sql); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql, 300); - - $this->assertEquals($expected, $db->sql_fetchrow($result)); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql); - - $no_cache_result = $db->sql_fetchrow($result); - $this->assertSame(false, $no_cache_result); - - $db->sql_close(); - } } diff --git a/tests/cache/common_test_case.php b/tests/cache/common_test_case.php index 45cf80e424..fa298ec9ae 100644 --- a/tests/cache/common_test_case.php +++ b/tests/cache/common_test_case.php @@ -61,4 +61,37 @@ abstract class phpbb_cache_common_test_case extends phpbb_database_test_case $this->driver->get('second_key') ); } + + public function test_cache_sql() + { + global $db, $cache; + $db = $this->new_dbal(); + $cache = new phpbb_cache_service($this->driver); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + + $result = $db->sql_query($sql, 300); + $first_result = $db->sql_fetchrow($result); + $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); + $this->assertEquals($expected, $first_result); + + $sql = 'DELETE FROM phpbb_config'; + $result = $db->sql_query($sql); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql, 300); + + $this->assertEquals($expected, $db->sql_fetchrow($result)); + + $sql = "SELECT * FROM phpbb_config + WHERE config_name = 'foo'"; + $result = $db->sql_query($sql); + + $no_cache_result = $db->sql_fetchrow($result); + $this->assertSame(false, $no_cache_result); + + $db->sql_close(); + } } diff --git a/tests/cache/file_driver_test.php b/tests/cache/file_driver_test.php index 2353940277..745c6bb081 100644 --- a/tests/cache/file_driver_test.php +++ b/tests/cache/file_driver_test.php @@ -66,42 +66,4 @@ class phpbb_cache_file_driver_test extends phpbb_cache_common_test_case } rmdir($this->cache_dir); } - - public function test_cache_sql() - { - global $db, $cache; - $db = $this->new_dbal(); - $cache = new phpbb_cache_service($this->driver); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - - $cache_path = $this->cache_dir . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)) . '.php'; - $this->assertFileNotExists($cache_path); - - $result = $db->sql_query($sql, 300); - $first_result = $db->sql_fetchrow($result); - $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); - $this->assertEquals($expected, $first_result); - - $this->assertFileExists($cache_path); - - $sql = 'DELETE FROM phpbb_config'; - $result = $db->sql_query($sql); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql, 300); - - $this->assertEquals($expected, $db->sql_fetchrow($result)); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql); - - $no_cache_result = $db->sql_fetchrow($result); - $this->assertSame(false, $no_cache_result); - - $db->sql_close(); - } } diff --git a/tests/cache/redis_driver_test.php b/tests/cache/redis_driver_test.php index cd24e33baf..c59d5e1929 100644 --- a/tests/cache/redis_driver_test.php +++ b/tests/cache/redis_driver_test.php @@ -46,46 +46,4 @@ class phpbb_cache_redis_driver_test extends phpbb_cache_common_test_case $this->driver = new phpbb_cache_driver_redis(self::$config['host'], self::$config['port']); $this->driver->purge(); } - - public function test_cache_sql() - { - global $db, $cache; - $db = $this->new_dbal(); - $cache = new phpbb_cache_service($this->driver); - - $redis = new Redis(); - $ok = $redis->connect(self::$config['host'], self::$config['port']); - $this->assertTrue($ok); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - - $key = $this->driver->key_prefix . 'sql_' . md5(preg_replace('/[\n\r\s\t]+/', ' ', $sql)); - $this->assertFalse($redis->exists($key)); - - $result = $db->sql_query($sql, 300); - $first_result = $db->sql_fetchrow($result); - $expected = array('config_name' => 'foo', 'config_value' => '23', 'is_dynamic' => 0); - $this->assertEquals($expected, $first_result); - - $this->assertTrue($redis->exists($key)); - - $sql = 'DELETE FROM phpbb_config'; - $result = $db->sql_query($sql); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql, 300); - - $this->assertEquals($expected, $db->sql_fetchrow($result)); - - $sql = "SELECT * FROM phpbb_config - WHERE config_name = 'foo'"; - $result = $db->sql_query($sql); - - $no_cache_result = $db->sql_fetchrow($result); - $this->assertSame(false, $no_cache_result); - - $db->sql_close(); - } } From f08c28c77a585e35cc17a2248ba61428275ccdd7 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 01:59:27 -0500 Subject: [PATCH 1111/1142] [ticket/10103] Factor out flock lock class. PHPBB3-10103 --- phpBB/includes/functions_messenger.php | 58 ------------ phpBB/includes/lock/flock.php | 124 +++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 58 deletions(-) create mode 100644 phpBB/includes/lock/flock.php diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index cf03de08c4..503f419e5a 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -650,64 +650,6 @@ class queue $this->data[$object]['data'][] = $scope; } - /** - * Obtains exclusive lock on queue cache file. - * Returns resource representing the lock - */ - function lock() - { - // For systems that can't have two processes opening - // one file for writing simultaneously - if (file_exists($this->cache_file . '.lock')) - { - $mode = 'rb'; - } - else - { - $mode = 'wb'; - } - - $lock_fp = @fopen($this->cache_file . '.lock', $mode); - - if ($mode == 'wb') - { - if (!$lock_fp) - { - // Two processes may attempt to create lock file at the same time. - // Have the losing process try opening the lock file again for reading - // on the assumption that the winning process created it - $mode = 'rb'; - $lock_fp = @fopen($this->cache_file . '.lock', $mode); - } - else - { - // Only need to set mode when the lock file is written - @chmod($this->cache_file . '.lock', 0666); - } - } - - if ($lock_fp) - { - @flock($lock_fp, LOCK_EX); - } - - return $lock_fp; - } - - /** - * Releases lock on queue cache file, using resource obtained from lock() - */ - function unlock($lock_fp) - { - // lock() will return null if opening lock file, and thus locking, failed. - // Accept null values here so that client code does not need to check them - if ($lock_fp) - { - @flock($lock_fp, LOCK_UN); - fclose($lock_fp); - } - } - /** * Process queue * Using lock file diff --git a/phpBB/includes/lock/flock.php b/phpBB/includes/lock/flock.php new file mode 100644 index 0000000000..e24a5f3e1c --- /dev/null +++ b/phpBB/includes/lock/flock.php @@ -0,0 +1,124 @@ +path = $path; + $this->lock_fp = null; + } + + /** + * Tries to acquire the lock. + * + * As a lock may only be held by one process at a time, lock + * acquisition may fail if another process is holding the lock. + * + * @return bool true if lock was acquired + * false otherwise + */ + public function acquire() + { + if ($this->locked) + { + return false; + } + + // For systems that can't have two processes opening + // one file for writing simultaneously + if (file_exists($this->path . '.lock')) + { + $mode = 'rb'; + } + else + { + $mode = 'wb'; + } + + $this->lock_fp = @fopen($this->path . '.lock', $mode); + + if ($mode == 'wb') + { + if (!$this->lock_fp) + { + // Two processes may attempt to create lock file at the same time. + // Have the losing process try opening the lock file again for reading + // on the assumption that the winning process created it + $mode = 'rb'; + $this->lock_fp = @fopen($this->path . '.lock', $mode); + } + else + { + // Only need to set mode when the lock file is written + @chmod($this->path . '.lock', 0666); + } + } + + if ($this->lock_fp) + { + @flock($this->lock_fp, LOCK_EX); + } + + return (bool) $this->lock_fp; + } + + /** + * Releases the lock. + * + * The lock must have been previously obtained, that is, acquire() call + * was issued and returned true. + * + * Note: Attempting to release a lock that is already released, + * that is, calling release() multiple times, is harmless. + * + * @return null + */ + public function release() + { + if ($this->lock_fp) + { + @flock($this->lock_fp, LOCK_UN); + fclose($this->lock_fp); + $this->lock_fp = null; + } + } +} From 4010f4085aa56f20fd95274ecfc2fe7404f98269 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 02:00:27 -0500 Subject: [PATCH 1112/1142] [ticket/10103] Use flock lock class in messenger. PHPBB3-10103 --- phpBB/includes/functions_messenger.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index 503f419e5a..56fc7e628f 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -658,13 +658,14 @@ class queue { global $db, $config, $phpEx, $phpbb_root_path, $user; - $lock_fp = $this->lock(); + $lock = new phpbb_lock_flock($this->cache_file); + $lock->acquire(); set_config('last_queue_run', time(), true); if (!file_exists($this->cache_file) || filemtime($this->cache_file) > time() - $config['queue_interval']) { - $this->unlock($lock_fp); + $lock->release(); return; } @@ -731,7 +732,7 @@ class queue break; default: - $this->unlock($lock_fp); + $lock->release(); return; } @@ -807,7 +808,7 @@ class queue } } - $this->unlock($lock_fp); + $lock->release(); } /** @@ -820,7 +821,8 @@ class queue return; } - $lock_fp = $this->lock(); + $lock = new phpbb_lock_flock($this->cache_file); + $lock->acquire(); if (file_exists($this->cache_file)) { @@ -847,7 +849,7 @@ class queue phpbb_chmod($this->cache_file, CHMOD_READ | CHMOD_WRITE); } - $this->unlock($lock_fp); + $lock->release(); } } From f72e435759e8fafe3b06af35072c1907ba016546 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 02:21:53 -0500 Subject: [PATCH 1113/1142] [ticket/10103] Test for flock lock class, with concurrency no less. PHPBB3-10103 --- tests/lock/flock_test.php | 108 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/lock/flock_test.php diff --git a/tests/lock/flock_test.php b/tests/lock/flock_test.php new file mode 100644 index 0000000000..5c645de27c --- /dev/null +++ b/tests/lock/flock_test.php @@ -0,0 +1,108 @@ +acquire(); + $this->assertTrue($ok); + $lock->release(); + } + + public function test_consecutive_locking() + { + $path = __DIR__ . '/../tmp/precious'; + + $lock = new phpbb_lock_flock($path); + $ok = $lock->acquire(); + $this->assertTrue($ok); + $lock->release(); + + $ok = $lock->acquire(); + $this->assertTrue($ok); + $lock->release(); + + $ok = $lock->acquire(); + $this->assertTrue($ok); + $lock->release(); + } + + /* This hangs the process. + public function test_concurrent_locking_fail() + { + $path = __DIR__ . '/../tmp/precious'; + + $lock1 = new phpbb_lock_flock($path); + $ok = $lock1->acquire(); + $this->assertTrue($ok); + + $lock2 = new phpbb_lock_flock($path); + $ok = $lock2->acquire(); + $this->assertFalse($ok); + + $lock->release(); + $ok = $lock2->acquire(); + $this->assertTrue($ok); + } + */ + + public function test_concurrent_locking() + { + if (!function_exists('pcntl_fork')) + { + $this->markTestSkipped('pcntl extension and pcntl_fork are required for this test'); + } + + $path = __DIR__ . '/../tmp/precious'; + + if ($pid = pcntl_fork()) + { + // parent + // wait 0.5 s, acquire the lock, note how long it took + sleep(0.5); + + $lock = new phpbb_lock_flock($path); + $start = time(); + $ok = $lock->acquire(); + $delta = time() - $start; + $this->assertTrue($ok); + $this->assertTrue($delta > 0.5); + + $lock->release(); + + // acquire again, this should be instantaneous + $start = time(); + $ok = $lock->acquire(); + $delta = time() - $start; + $this->assertTrue($ok); + $this->assertTrue($delta < 0.1); + + // reap the child + $status = null; + pcntl_waitpid($pid, $status); + } + else + { + // child + // immediately acquire the lock and sleep for 2 s + $lock = new phpbb_lock_flock($path); + $ok = $lock->acquire(); + $this->assertTrue($ok); + sleep(2); + $lock->release(); + + // and go away silently + pcntl_exec('/usr/bin/env', array('true')); + } + } +} From 318140b4d6257dd49ae86ed1185568f4d523e53e Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 02:26:55 -0500 Subject: [PATCH 1114/1142] [ticket/10103] Convert the rest of the tree to flock class. PHPBB3-10103 --- phpBB/includes/cache/driver/file.php | 16 +++++++++++----- phpBB/includes/template/compile.php | 8 +++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php index a0f06dde4b..ee1b430451 100644 --- a/phpBB/includes/cache/driver/file.php +++ b/phpBB/includes/cache/driver/file.php @@ -653,10 +653,11 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base $file = "{$this->cache_dir}$filename.$phpEx"; + $lock = new phpbb_lock_flock($file); + $lock->acquire(); + if ($handle = @fopen($file, 'wb')) { - @flock($handle, LOCK_EX); - // File header fwrite($handle, '<' . '?php exit; ?' . '>'); @@ -697,7 +698,6 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base fwrite($handle, $data); } - @flock($handle, LOCK_UN); fclose($handle); if (!function_exists('phpbb_chmod')) @@ -708,10 +708,16 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE); - return true; + $rv = true; + } + else + { + $rv = false; } - return false; + $lock->release(); + + return $rv; } /** diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php index d0b3d0f115..22da21820e 100644 --- a/phpBB/includes/template/compile.php +++ b/phpBB/includes/template/compile.php @@ -58,6 +58,9 @@ class phpbb_template_compile */ public function compile_file_to_file($source_file, $compiled_file) { + $lock = new phpbb_lock_flock($compiled_file); + $lock->acquire(); + $source_handle = @fopen($source_file, 'rb'); $destination_handle = @fopen($compiled_file, 'wb'); @@ -66,16 +69,15 @@ class phpbb_template_compile return false; } - @flock($destination_handle, LOCK_EX); - $this->compile_stream_to_stream($source_handle, $destination_handle); @fclose($source_handle); - @flock($destination_handle, LOCK_UN); @fclose($destination_handle); phpbb_chmod($compiled_file, CHMOD_READ | CHMOD_WRITE); + $lock->release(); + clearstatcache(); return true; From fc410e1cd0071a2de3dc90ce62a5abbef0266f15 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 02:38:57 -0500 Subject: [PATCH 1115/1142] [ticket/10103] Try a longer sleep for travis. Apparently travis takes longer than half a second to fork php. PHPBB3-10103 --- tests/lock/flock_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lock/flock_test.php b/tests/lock/flock_test.php index 5c645de27c..bc682f9410 100644 --- a/tests/lock/flock_test.php +++ b/tests/lock/flock_test.php @@ -69,7 +69,7 @@ class phpbb_lock_flock_test extends phpbb_test_case { // parent // wait 0.5 s, acquire the lock, note how long it took - sleep(0.5); + sleep(1); $lock = new phpbb_lock_flock($path); $start = time(); From cf3080b83ed6f90b24cfd99a9fc7dd71dddde827 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 02:39:50 -0500 Subject: [PATCH 1116/1142] [ticket/9983] Correct incorrect markTestSkipped call. PHPBB3-9983 --- tests/cache/redis_driver_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cache/redis_driver_test.php b/tests/cache/redis_driver_test.php index cd24e33baf..a90a750460 100644 --- a/tests/cache/redis_driver_test.php +++ b/tests/cache/redis_driver_test.php @@ -35,7 +35,7 @@ class phpbb_cache_redis_driver_test extends phpbb_cache_common_test_case } else { - $this->markTestSkipped('Test redis host/port is not specified'); + self::markTestSkipped('Test redis host/port is not specified'); } } From b67f112e03cf37436bdd650ea4b9a75b35000fdd Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 02:52:18 -0500 Subject: [PATCH 1117/1142] [ticket/10091] Bump minimum supported postgresql version to 8.3. PHPBB3-10091 --- phpBB/docs/INSTALL.html | 2 +- phpBB/docs/coding-guidelines.html | 2 +- phpBB/includes/functions_install.php | 2 +- phpBB/language/en/install.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/docs/INSTALL.html b/phpBB/docs/INSTALL.html index 07f0cbc8c2..58aeb904c7 100644 --- a/phpBB/docs/INSTALL.html +++ b/phpBB/docs/INSTALL.html @@ -132,7 +132,7 @@
  • A SQL database system, one of:
    • MySQL 3.23 or above (MySQLi supported)
    • -
    • PostgreSQL 7.3+
    • +
    • PostgreSQL 8.3+
    • SQLite 2.8.2+ (SQLite 3 is not supported)
    • Firebird 2.1+
    • MS SQL Server 2000 or above (directly or via ODBC or the native adapter)
    • diff --git a/phpBB/docs/coding-guidelines.html b/phpBB/docs/coding-guidelines.html index 0e3a97c004..eb569d12d5 100644 --- a/phpBB/docs/coding-guidelines.html +++ b/phpBB/docs/coding-guidelines.html @@ -740,7 +740,7 @@ static private function f()

      2.iii. SQL/SQL Layout

      Common SQL Guidelines:

      -

      All SQL should be cross-DB compatible, if DB specific SQL is used alternatives must be provided which work on all supported DB's (MySQL3/4/5, MSSQL (7.0 and 2000), PostgreSQL (7.0+), Firebird, SQLite, Oracle8, ODBC (generalised if possible)).

      +

      All SQL should be cross-DB compatible, if DB specific SQL is used alternatives must be provided which work on all supported DB's (MySQL3/4/5, MSSQL (7.0 and 2000), PostgreSQL (8.3+), Firebird, SQLite, Oracle8, ODBC (generalised if possible)).

      All SQL commands should utilise the DataBase Abstraction Layer (DBAL)

      SQL code layout:

      diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php index 7a799993db..ab6b3ea009 100644 --- a/phpBB/includes/functions_install.php +++ b/phpBB/includes/functions_install.php @@ -87,7 +87,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 '2.0.x' => false, ), 'postgres' => array( - 'LABEL' => 'PostgreSQL 7.x/8.x', + 'LABEL' => 'PostgreSQL 8.3+', 'SCHEMA' => 'postgres', 'MODULE' => 'pgsql', 'DELIM' => ';', diff --git a/phpBB/language/en/install.php b/phpBB/language/en/install.php index cb6a17afa2..1c45deae11 100644 --- a/phpBB/language/en/install.php +++ b/phpBB/language/en/install.php @@ -210,7 +210,7 @@ $lang = array_merge($lang, array(

      phpBB3 supports the following databases:

      • MySQL 3.23 or above (MySQLi supported)
      • -
      • PostgreSQL 7.3+
      • +
      • PostgreSQL 8.3+
      • SQLite 2.8.2+
      • Firebird 2.1+
      • MS SQL Server 2000 or above (directly or via ODBC)
      • From af2887f3a7f27b33c2ecd14d4baab846b71ddb62 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 03:34:51 -0500 Subject: [PATCH 1118/1142] [ticket/10716] php parse all php files as part of the test suite. PHPBB3-10716 --- tests/lint_test.php | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/lint_test.php diff --git a/tests/lint_test.php b/tests/lint_test.php new file mode 100644 index 0000000000..57c78ae809 --- /dev/null +++ b/tests/lint_test.php @@ -0,0 +1,49 @@ +check($root); + } + + protected function check($root) + { + $dh = opendir($root); + while (($filename = readdir($dh)) !== false) + { + if ($filename == '.' || $filename == '..' || $filename == 'git') + { + continue; + } + $path = $root . '/' . $filename; + // skip symlinks to avoid infinite loops + if (is_link($path)) + { + continue; + } + if (is_dir($path)) + { + $this->check($path); + } + else if (substr($filename, strlen($filename)-4) == '.php') + { + // assume php binary is called php and it is in PATH + $cmd = 'php -l ' . escapeshellarg($path); + $output = array(); + $status = 1; + exec($cmd, $output, $status); + $output = implode("\n", $output); + $this->assertEquals(0, $status, "php -l failed for $path:\n$output"); + } + } + } +} From 4cc81f1ffa62cdbcbb656300c7dd9fbd44cc21fc Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 13:44:22 -0500 Subject: [PATCH 1119/1142] [ticket/10103] Correct flock class documentation. PHPBB3-10103 --- phpBB/includes/lock/flock.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/phpBB/includes/lock/flock.php b/phpBB/includes/lock/flock.php index e24a5f3e1c..09450644bc 100644 --- a/phpBB/includes/lock/flock.php +++ b/phpBB/includes/lock/flock.php @@ -35,9 +35,9 @@ class phpbb_lock_flock private $lock_fp; /** - * Creates an instance of the lock. + * Constructor. * - * You have to call acquire() to actually create the lock. + * You have to call acquire() to actually acquire the lock. * * @param string $path Path to the file access to which is controlled */ @@ -50,8 +50,17 @@ class phpbb_lock_flock /** * Tries to acquire the lock. * - * As a lock may only be held by one process at a time, lock - * acquisition may fail if another process is holding the lock. + * If the lock is already held by another process, this call will block + * until the other process releases the lock. If a lock is acquired and + * is not released before script finishes but the process continues to + * live (apache/fastcgi) then subsequent processes trying to acquire + * the same lock will be blocked forever. + * + * If the lock is already held by the same process via another instance + * of this class, this call will block forever. + * + * If flock function is disabled in php or fails to work, lock + * acquisition will fail and false will be returned. * * @return bool true if lock was acquired * false otherwise From 3924676f2bd1a30ff56ab1db985cf622f1fac286 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 13:45:02 -0500 Subject: [PATCH 1120/1142] [ticket/10103] $rv had too few characters. PHPBB3-10103 --- phpBB/includes/cache/driver/file.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php index ee1b430451..691abe0438 100644 --- a/phpBB/includes/cache/driver/file.php +++ b/phpBB/includes/cache/driver/file.php @@ -708,16 +708,16 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE); - $rv = true; + $return_value = true; } else { - $rv = false; + $return_value = false; } $lock->release(); - return $rv; + return $return_value; } /** From a553cfbc30472f136f9904d97bf4d09a7187518f Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 13:46:01 -0500 Subject: [PATCH 1121/1142] [ticket/10103] Inline assignment is bad? PHPBB3-10103 --- tests/lock/flock_test.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/lock/flock_test.php b/tests/lock/flock_test.php index bc682f9410..abcb4e79c2 100644 --- a/tests/lock/flock_test.php +++ b/tests/lock/flock_test.php @@ -65,7 +65,8 @@ class phpbb_lock_flock_test extends phpbb_test_case $path = __DIR__ . '/../tmp/precious'; - if ($pid = pcntl_fork()) + $pid = pcntl_fork(); + if ($pid) { // parent // wait 0.5 s, acquire the lock, note how long it took From 285feb49f82084b5b489aa1fc6765b9b02ea1b29 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 13:47:57 -0500 Subject: [PATCH 1122/1142] [ticket/10103] assertLessThan/assertGreaterThan. PHPBB3-10103 --- tests/lock/flock_test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lock/flock_test.php b/tests/lock/flock_test.php index abcb4e79c2..8bb7d0a041 100644 --- a/tests/lock/flock_test.php +++ b/tests/lock/flock_test.php @@ -77,7 +77,7 @@ class phpbb_lock_flock_test extends phpbb_test_case $ok = $lock->acquire(); $delta = time() - $start; $this->assertTrue($ok); - $this->assertTrue($delta > 0.5); + $this->assertGreaterThan(0.5, $delta); $lock->release(); @@ -86,7 +86,7 @@ class phpbb_lock_flock_test extends phpbb_test_case $ok = $lock->acquire(); $delta = time() - $start; $this->assertTrue($ok); - $this->assertTrue($delta < 0.1); + $this->assertLessThan(0.1, $delta); // reap the child $status = null; From e22dd7dfadedd951d1fd17b61fa700c572ca4d79 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 13:50:35 -0500 Subject: [PATCH 1123/1142] [ticket/10103] Assert with messages. PHPBB3-10103 --- tests/lock/flock_test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lock/flock_test.php b/tests/lock/flock_test.php index 8bb7d0a041..1edc96b3a4 100644 --- a/tests/lock/flock_test.php +++ b/tests/lock/flock_test.php @@ -77,7 +77,7 @@ class phpbb_lock_flock_test extends phpbb_test_case $ok = $lock->acquire(); $delta = time() - $start; $this->assertTrue($ok); - $this->assertGreaterThan(0.5, $delta); + $this->assertGreaterThan(0.5, $delta, 'First lock acquired too soon'); $lock->release(); @@ -86,7 +86,7 @@ class phpbb_lock_flock_test extends phpbb_test_case $ok = $lock->acquire(); $delta = time() - $start; $this->assertTrue($ok); - $this->assertLessThan(0.1, $delta); + $this->assertLessThan(0.1, $delta, 'Second lock not acquired instantaneously'); // reap the child $status = null; From 4133fae99ec4146a01c67f6a6b3020020d1c6464 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 13:58:14 -0500 Subject: [PATCH 1124/1142] [ticket/10716] Only lint on php 5.3+. PHPBB3-10716 --- tests/lint_test.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/lint_test.php b/tests/lint_test.php index 57c78ae809..1642b571dd 100644 --- a/tests/lint_test.php +++ b/tests/lint_test.php @@ -11,6 +11,11 @@ class phpbb_lint_test extends phpbb_test_case { public function test_lint() { + if (version_compare(PHP_VERSION, '5.3.0', '<')) + { + $this->markTestSkipped('phpBB uses PHP 5.3 syntax in some files, linting on PHP < 5.3 will fail'); + } + $root = dirname(__FILE__) . '/..'; $this->check($root); } From 3e093c282a63df4d16b212859fd8137fd2bbca81 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 14:05:49 -0500 Subject: [PATCH 1125/1142] [ticket/10103] New and improved wording. PHPBB3-10103 --- phpBB/includes/lock/flock.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/lock/flock.php b/phpBB/includes/lock/flock.php index 09450644bc..5c2288ce1b 100644 --- a/phpBB/includes/lock/flock.php +++ b/phpBB/includes/lock/flock.php @@ -22,7 +22,7 @@ if (!defined('IN_PHPBB')) class phpbb_lock_flock { /** - * Path to the file access to which is controlled + * Path to the file to which access is controlled * * @var string */ @@ -39,7 +39,7 @@ class phpbb_lock_flock * * You have to call acquire() to actually acquire the lock. * - * @param string $path Path to the file access to which is controlled + * @param string $path Path to the file to which access is controlled */ public function __construct($path) { From 8897efe087195ba31920c6b3aadbe8ea1b393c12 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 16:42:58 -0500 Subject: [PATCH 1126/1142] [ticket/10716] Exclude our dependencies from linting. PHPBB3-10716 --- tests/lint_test.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/lint_test.php b/tests/lint_test.php index 1642b571dd..67b7413cb4 100644 --- a/tests/lint_test.php +++ b/tests/lint_test.php @@ -9,6 +9,17 @@ class phpbb_lint_test extends phpbb_test_case { + static protected $exclude; + + static public function setUpBeforeClass() + { + self::$exclude = array( + // PHP Fatal error: Cannot declare class Container because the name is already in use in /var/www/projects/phpbb3/tests/../phpBB/vendor/symfony/dependency-injection/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php on line 20 + // https://gist.github.com/e003913ffd493da63cbc + dirname(__FILE__) . '/../phpBB/vendor', + ); + } + public function test_lint() { if (version_compare(PHP_VERSION, '5.3.0', '<')) @@ -35,7 +46,7 @@ class phpbb_lint_test extends phpbb_test_case { continue; } - if (is_dir($path)) + if (is_dir($path) && !in_array($path, self::$exclude)) { $this->check($path); } From 8ea52b56197cc9a234d6b0ce3ddd10820feb6dd1 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 17:43:36 -0500 Subject: [PATCH 1127/1142] [ticket/10716] Skip test if php is not in PATH. PHPBB3-10716 --- tests/lint_test.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/lint_test.php b/tests/lint_test.php index 67b7413cb4..d73ab7fedd 100644 --- a/tests/lint_test.php +++ b/tests/lint_test.php @@ -13,6 +13,14 @@ class phpbb_lint_test extends phpbb_test_case static public function setUpBeforeClass() { + $output = array(); + $status = 1; + exec('php -v', $output, $status); + if ($status) + { + self::markTestSkipped("php is not in PATH or broken: $output"); + } + self::$exclude = array( // PHP Fatal error: Cannot declare class Container because the name is already in use in /var/www/projects/phpbb3/tests/../phpBB/vendor/symfony/dependency-injection/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php on line 20 // https://gist.github.com/e003913ffd493da63cbc From fb261e19ffc3bf19477510fa3877a8d9ea251655 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 18:52:27 -0500 Subject: [PATCH 1128/1142] [ticket/10716] Collect standard error from executed php process. php executes everything via a shell. The standard error of this top level shell is not captured by exec/shell_exec/popen/etc. and there is no way to capture it. proc_open might work but it is a nightmare to use and without multiplexing reads from standard error and standard output it can deadlock. Thus the solution in this commit. Put the command into a subshell and redirect standard error to standard output for the subshell. PHPBB3-10716 --- tests/lint_test.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/lint_test.php b/tests/lint_test.php index d73ab7fedd..905067072d 100644 --- a/tests/lint_test.php +++ b/tests/lint_test.php @@ -15,9 +15,10 @@ class phpbb_lint_test extends phpbb_test_case { $output = array(); $status = 1; - exec('php -v', $output, $status); + exec('(php -v) 2>&1', $output, $status); if ($status) { + $output = implode("\n", $output); self::markTestSkipped("php is not in PATH or broken: $output"); } @@ -61,7 +62,7 @@ class phpbb_lint_test extends phpbb_test_case else if (substr($filename, strlen($filename)-4) == '.php') { // assume php binary is called php and it is in PATH - $cmd = 'php -l ' . escapeshellarg($path); + $cmd = '(php -l ' . escapeshellarg($path) . ') 2>&1'; $output = array(); $status = 1; exec($cmd, $output, $status); From 03f819862f15efa2ef64331b23394086746d09be Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 00:41:47 -0500 Subject: [PATCH 1129/1142] [ticket/10602] Use last_queue_run for its intended purpose. We keep the last queue run time around, therefore for determining whether enough time has passed since the last run we can simply use this config variable. When there is no queue file we consider a queue run successful. Previously queue.php ("cache file") modification time would be used for determining whether enough time has passed since last queue run. The problem was that modification time would be updated whenever anything was added to the queue, creating a situation where if queue is processed less frequently than it is added to that email would not be sent. PHPBB3-10602 --- phpBB/includes/functions_messenger.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index 6549693333..ae0f7823cc 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -715,14 +715,19 @@ class queue $lock_fp = $this->lock(); - set_config('last_queue_run', time(), true); - - if (!file_exists($this->cache_file) || filemtime($this->cache_file) > time() - $config['queue_interval']) + if (!file_exists($this->cache_file) || $config['last_queue_run'] > time() - $config['queue_interval']) { + if (!file_exists($this->cache_file)) + { + set_config('last_queue_run', time(), true); + } + $this->unlock($lock_fp); return; } + set_config('last_queue_run', time(), true); + include($this->cache_file); foreach ($this->queue_data as $object => $data_ary) From 1e50116c54ec7ffbaba4622d5481207423ef2bbe Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 00:57:24 -0500 Subject: [PATCH 1130/1142] [ticket/10602] Avoid a race condition. PHPBB3-10602 --- phpBB/includes/functions_messenger.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index ae0f7823cc..e837811c86 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -715,9 +715,11 @@ class queue $lock_fp = $this->lock(); - if (!file_exists($this->cache_file) || $config['last_queue_run'] > time() - $config['queue_interval']) + // avoid races, check file existence once + $have_cache_file = file_exists($this->cache_file); + if (!$have_cache_file || $config['last_queue_run'] > time() - $config['queue_interval']) { - if (!file_exists($this->cache_file)) + if (!$have_cache_file) { set_config('last_queue_run', time(), true); } From a8d02ffc27d8cee916f267c91488276fca3606e7 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 01:54:29 -0500 Subject: [PATCH 1131/1142] [ticket/11247] Fix wrong property reference in flock class. PHPBB3-11247 --- phpBB/includes/lock/flock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/lock/flock.php b/phpBB/includes/lock/flock.php index 5c2288ce1b..97bc7dd2b9 100644 --- a/phpBB/includes/lock/flock.php +++ b/phpBB/includes/lock/flock.php @@ -67,7 +67,7 @@ class phpbb_lock_flock */ public function acquire() { - if ($this->locked) + if ($this->lock_fp) { return false; } From af064cdaad357f705fc7c80f4d018d57cffa8d33 Mon Sep 17 00:00:00 2001 From: Senky Date: Wed, 16 May 2012 23:02:20 +0200 Subject: [PATCH 1132/1142] [ticket/10841] Modifying style and language selectors in UCP Commit also deletes all unnecessary blank spaces at the end of the lines in both ucp_prefs_personal.html PHPBB3-10841 --- phpBB/includes/ucp/ucp_prefs.php | 30 +++++++++++++ .../template/ucp_prefs_personal.html | 42 ++++++++++--------- .../template/ucp_prefs_personal.html | 18 ++++---- 3 files changed, 62 insertions(+), 28 deletions(-) diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 17d7d23f02..19e1b45787 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -134,6 +134,33 @@ class ucp_prefs } $dateformat_options .= '>' . $user->lang['CUSTOM_DATEFORMAT'] . ''; + // check for count of installed languages + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE; + $result = $db->sql_query($sql); + if( $db->sql_affectedrows() > 1 ) + { + $s_more_languages = true; + } + else + { + $s_more_languages = false; + } + + // check for count of installed and active styles + $sql = 'SELECT style_id + FROM ' . STYLES_TABLE . ' + WHERE style_active = 1'; + $result = $db->sql_query($sql); + if( $db->sql_affectedrows() > 1 ) + { + $s_more_styles = true; + } + else + { + $s_more_styles = false; + } + $template->assign_vars(array( 'ERROR' => (sizeof($error)) ? implode('
        ', $error) : '', @@ -155,6 +182,9 @@ class ucp_prefs 'DEFAULT_DATEFORMAT' => $config['default_dateformat'], 'A_DEFAULT_DATEFORMAT' => addslashes($config['default_dateformat']), + 'S_MORE_LANGUAGES' => $s_more_languages, + 'S_MORE_STYLES' => $s_more_styles, + 'S_LANG_OPTIONS' => language_select($data['lang']), 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : style_select($data['style']), 'S_TZ_OPTIONS' => tz_select($data['tz'], true), diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index 9022346627..416343e57d 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -12,21 +12,21 @@
        - +
        - +

        {L_ALLOW_PM_EXPLAIN}
        - +
        @@ -34,17 +34,17 @@

        {L_HIDE_ONLINE_EXPLAIN}
        - +
        - +
        - - + +
        @@ -52,22 +52,24 @@
        - +
        - +
        -
        -
        -
        -
        - + +
        +
        +
        +
        + +
        @@ -80,7 +82,7 @@
        - +
        @@ -97,9 +99,9 @@ - +
        - {S_HIDDEN_FIELDS}  + {S_HIDDEN_FIELDS}  {S_FORM_TOKEN}
        @@ -113,9 +115,9 @@ function customDates() { var e = document.getElementById('dateoptions'); - + e.selectedIndex = e.length - 1; - + // Loop and match date_format in menu for (var i = 0; i < e.length; i++) { @@ -125,7 +127,7 @@ break; } } - + // Show/hide custom field if (e.selectedIndex == e.length - 1) { diff --git a/phpBB/styles/subsilver2/template/ucp_prefs_personal.html b/phpBB/styles/subsilver2/template/ucp_prefs_personal.html index e2266b7d38..c604671c5c 100644 --- a/phpBB/styles/subsilver2/template/ucp_prefs_personal.html +++ b/phpBB/styles/subsilver2/template/ucp_prefs_personal.html @@ -29,43 +29,45 @@ {ERROR} - + {L_SHOW_EMAIL}: checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_ADMIN_EMAIL}: checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_ALLOW_PM}:
        {L_ALLOW_PM_EXPLAIN} checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_HIDE_ONLINE}:
        {L_HIDE_ONLINE_EXPLAIN} checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_NOTIFY_METHOD}:
        {L_NOTIFY_METHOD_EXPLAIN} checked="checked" />{L_NOTIFY_METHOD_EMAIL}   checked="checked" />{L_NOTIFY_METHOD_IM}   checked="checked" />{L_NOTIFY_METHOD_BOTH} - + {L_NOTIFY_ON_PM}: checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_POPUP_ON_PM}: checked="checked" />{L_YES}   checked="checked" />{L_NO} + {L_BOARD_LANGUAGE}: - + + {L_BOARD_STYLE}: From dd6983b14bba4326d824b3abb130eafc5e8f666c Mon Sep 17 00:00:00 2001 From: Senky Date: Thu, 17 May 2012 20:01:44 +0200 Subject: [PATCH 1133/1142] [ticket/10841] changing affectedrows check to COUNT in sql this sould reduce load and be faster. Also freeresult functions added PHPBB3-10841 --- phpBB/includes/ucp/ucp_prefs.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 19e1b45787..b95d2d4ee8 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -134,11 +134,11 @@ class ucp_prefs } $dateformat_options .= '>' . $user->lang['CUSTOM_DATEFORMAT'] . ''; - // check for count of installed languages - $sql = 'SELECT lang_id + // check if there are any user-selectable languages + $sql = 'SELECT COUNT(lang_id) as languages_count FROM ' . LANG_TABLE; $result = $db->sql_query($sql); - if( $db->sql_affectedrows() > 1 ) + if( $db->sql_fetchfield('languages_count') > 1 ) { $s_more_languages = true; } @@ -146,13 +146,14 @@ class ucp_prefs { $s_more_languages = false; } + $db->sql_freeresult($result); - // check for count of installed and active styles - $sql = 'SELECT style_id + // check if there are any user-selectable styles + $sql = 'SELECT COUNT(style_id) as styles_count FROM ' . STYLES_TABLE . ' WHERE style_active = 1'; $result = $db->sql_query($sql); - if( $db->sql_affectedrows() > 1 ) + if( $db->sql_fetchfield('styles_count') > 1 ) { $s_more_styles = true; } @@ -160,6 +161,7 @@ class ucp_prefs { $s_more_styles = false; } + $db->sql_freeresult($result); $template->assign_vars(array( 'ERROR' => (sizeof($error)) ? implode('
        ', $error) : '', From f7508c3f042091f163a87829a051312fa58630f1 Mon Sep 17 00:00:00 2001 From: Senky Date: Wed, 7 Nov 2012 10:28:25 +0100 Subject: [PATCH 1134/1142] [ticket/10841] removing unnecessary spacing PHPBB3-10841 --- phpBB/includes/ucp/ucp_prefs.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index b95d2d4ee8..e84c7e662a 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -138,7 +138,7 @@ class ucp_prefs $sql = 'SELECT COUNT(lang_id) as languages_count FROM ' . LANG_TABLE; $result = $db->sql_query($sql); - if( $db->sql_fetchfield('languages_count') > 1 ) + if($db->sql_fetchfield('languages_count') > 1) { $s_more_languages = true; } @@ -153,7 +153,7 @@ class ucp_prefs FROM ' . STYLES_TABLE . ' WHERE style_active = 1'; $result = $db->sql_query($sql); - if( $db->sql_fetchfield('styles_count') > 1 ) + if($db->sql_fetchfield('styles_count') > 1) { $s_more_styles = true; } From 120accb9d4b2a89ca05f712a54427e072584c2f9 Mon Sep 17 00:00:00 2001 From: Senky Date: Thu, 8 Nov 2012 17:30:58 +0100 Subject: [PATCH 1135/1142] [ticket/10841] adding space after if PHPBB3-10841 --- phpBB/includes/ucp/ucp_prefs.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index e84c7e662a..c6e43b831c 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -138,7 +138,7 @@ class ucp_prefs $sql = 'SELECT COUNT(lang_id) as languages_count FROM ' . LANG_TABLE; $result = $db->sql_query($sql); - if($db->sql_fetchfield('languages_count') > 1) + if ($db->sql_fetchfield('languages_count') > 1) { $s_more_languages = true; } @@ -153,7 +153,7 @@ class ucp_prefs FROM ' . STYLES_TABLE . ' WHERE style_active = 1'; $result = $db->sql_query($sql); - if($db->sql_fetchfield('styles_count') > 1) + if ($db->sql_fetchfield('styles_count') > 1) { $s_more_styles = true; } From 0793f8e2e60453db8de1ff8e231a36b2c9024f3d Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 11:54:49 -0500 Subject: [PATCH 1136/1142] [ticket/10841] Revert whitespace changes. PHPBB3-10841 --- .../template/ucp_prefs_personal.html | 26 +++++++++---------- .../template/ucp_prefs_personal.html | 14 +++++----- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index 416343e57d..01e0c9ba28 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -12,21 +12,21 @@
        - +
        - +

        {L_ALLOW_PM_EXPLAIN}
        - +
        @@ -34,17 +34,17 @@

        {L_HIDE_ONLINE_EXPLAIN}
        - +
        - +
        - - + +
        @@ -82,7 +82,7 @@
        - +
        @@ -99,9 +99,9 @@ - +
        - {S_HIDDEN_FIELDS}  + {S_HIDDEN_FIELDS}  {S_FORM_TOKEN}
        @@ -115,9 +115,9 @@ function customDates() { var e = document.getElementById('dateoptions'); - + e.selectedIndex = e.length - 1; - + // Loop and match date_format in menu for (var i = 0; i < e.length; i++) { @@ -127,7 +127,7 @@ break; } } - + // Show/hide custom field if (e.selectedIndex == e.length - 1) { diff --git a/phpBB/styles/subsilver2/template/ucp_prefs_personal.html b/phpBB/styles/subsilver2/template/ucp_prefs_personal.html index c604671c5c..4cd0f37a80 100644 --- a/phpBB/styles/subsilver2/template/ucp_prefs_personal.html +++ b/phpBB/styles/subsilver2/template/ucp_prefs_personal.html @@ -29,35 +29,35 @@ {ERROR} - + {L_SHOW_EMAIL}: checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_ADMIN_EMAIL}: checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_ALLOW_PM}:
        {L_ALLOW_PM_EXPLAIN} checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_HIDE_ONLINE}:
        {L_HIDE_ONLINE_EXPLAIN} checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_NOTIFY_METHOD}:
        {L_NOTIFY_METHOD_EXPLAIN} checked="checked" />{L_NOTIFY_METHOD_EMAIL}   checked="checked" />{L_NOTIFY_METHOD_IM}   checked="checked" />{L_NOTIFY_METHOD_BOTH} - + {L_NOTIFY_ON_PM}: checked="checked" />{L_YES}   checked="checked" />{L_NO} - + {L_POPUP_ON_PM}: checked="checked" />{L_YES}   checked="checked" />{L_NO} From a8e74f5292aaa74e2035dd5f3ab972f0201a1fe7 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 12:02:30 -0500 Subject: [PATCH 1137/1142] [ticket/10841] Revert more whitespace changes. PHPBB3-10841 --- phpBB/styles/prosilver/template/ucp_prefs_personal.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index 01e0c9ba28..600319fc72 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -52,14 +52,14 @@
        - +
        - +
        From dc649ad3cd8ea4520ad3694027679d6312c9495f Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 11:34:16 -0500 Subject: [PATCH 1138/1142] [ticket/11248] Line endings to LF. PHPBB3-11248 --- tests/session/append_sid_test.php | 101 +++++++++++++++--------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/tests/session/append_sid_test.php b/tests/session/append_sid_test.php index 88f6f0718e..ce7bf71215 100644 --- a/tests/session/append_sid_test.php +++ b/tests/session/append_sid_test.php @@ -1,51 +1,50 @@ - 1, 'f' => 2), true, false, 'viewtopic.php?t=1&f=2', 'parameters in params-argument as array'), - - // Custom sid parameter - array('viewtopic.php', 't=1&f=2', true, 'custom-sid', 'viewtopic.php?t=1&f=2&sid=custom-sid', 'using session_id'), - - // Testing anchors - array('viewtopic.php?t=1&f=2#anchor', false, true, false, 'viewtopic.php?t=1&f=2#anchor', 'anchor in url-argument'), - array('viewtopic.php', 't=1&f=2#anchor', true, false, 'viewtopic.php?t=1&f=2#anchor', 'anchor in params-argument'), - array('viewtopic.php', array('t' => 1, 'f' => 2, '#' => 'anchor'), true, false, 'viewtopic.php?t=1&f=2#anchor', 'anchor in params-argument (array)'), - - // Anchors and custom sid - array('viewtopic.php?t=1&f=2#anchor', false, true, 'custom-sid', 'viewtopic.php?t=1&f=2&sid=custom-sid#anchor', 'anchor in url-argument using session_id'), - array('viewtopic.php', 't=1&f=2#anchor', true, 'custom-sid', 'viewtopic.php?t=1&f=2&sid=custom-sid#anchor', 'anchor in params-argument using session_id'), - array('viewtopic.php', array('t' => 1, 'f' => 2, '#' => 'anchor'), true, 'custom-sid', 'viewtopic.php?t=1&f=2&sid=custom-sid#anchor', 'anchor in params-argument (array) using session_id'), - - // Empty parameters should not append the ? - array('viewtopic.php', false, true, false, 'viewtopic.php', 'no params using bool false'), - array('viewtopic.php', '', true, false, 'viewtopic.php', 'no params using empty string'), - array('viewtopic.php', array(), true, false, 'viewtopic.php', 'no params using empty array'), - ); - } - - /** - * @dataProvider append_sid_data - */ - public function test_append_sid($url, $params, $is_amp, $session_id, $expected, $description) - { - $this->assertEquals($expected, append_sid($url, $params, $is_amp, $session_id)); - } -} - + 1, 'f' => 2), true, false, 'viewtopic.php?t=1&f=2', 'parameters in params-argument as array'), + + // Custom sid parameter + array('viewtopic.php', 't=1&f=2', true, 'custom-sid', 'viewtopic.php?t=1&f=2&sid=custom-sid', 'using session_id'), + + // Testing anchors + array('viewtopic.php?t=1&f=2#anchor', false, true, false, 'viewtopic.php?t=1&f=2#anchor', 'anchor in url-argument'), + array('viewtopic.php', 't=1&f=2#anchor', true, false, 'viewtopic.php?t=1&f=2#anchor', 'anchor in params-argument'), + array('viewtopic.php', array('t' => 1, 'f' => 2, '#' => 'anchor'), true, false, 'viewtopic.php?t=1&f=2#anchor', 'anchor in params-argument (array)'), + + // Anchors and custom sid + array('viewtopic.php?t=1&f=2#anchor', false, true, 'custom-sid', 'viewtopic.php?t=1&f=2&sid=custom-sid#anchor', 'anchor in url-argument using session_id'), + array('viewtopic.php', 't=1&f=2#anchor', true, 'custom-sid', 'viewtopic.php?t=1&f=2&sid=custom-sid#anchor', 'anchor in params-argument using session_id'), + array('viewtopic.php', array('t' => 1, 'f' => 2, '#' => 'anchor'), true, 'custom-sid', 'viewtopic.php?t=1&f=2&sid=custom-sid#anchor', 'anchor in params-argument (array) using session_id'), + + // Empty parameters should not append the ? + array('viewtopic.php', false, true, false, 'viewtopic.php', 'no params using bool false'), + array('viewtopic.php', '', true, false, 'viewtopic.php', 'no params using empty string'), + array('viewtopic.php', array(), true, false, 'viewtopic.php', 'no params using empty array'), + ); + } + + /** + * @dataProvider append_sid_data + */ + public function test_append_sid($url, $params, $is_amp, $session_id, $expected, $description) + { + $this->assertEquals($expected, append_sid($url, $params, $is_amp, $session_id)); + } +} From dbb54b217b4d0c0669a566f9c950e8331887d276 Mon Sep 17 00:00:00 2001 From: Patrick Webster Date: Wed, 5 Dec 2012 22:57:06 -0600 Subject: [PATCH 1139/1142] [ticket/11219] Coding guidelines and naming consistency changes PHPBB3-11219 --- tests/dbal/write_sequence_test.php | 2 +- ...phpbb_database_test_connection_manager.php | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/dbal/write_sequence_test.php b/tests/dbal/write_sequence_test.php index d2c30b4e89..8975cfbfb1 100644 --- a/tests/dbal/write_sequence_test.php +++ b/tests/dbal/write_sequence_test.php @@ -13,7 +13,7 @@ class phpbb_dbal_write_sequence_test extends phpbb_database_test_case { public function getDataSet() { - return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/three_users.xml'); + return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/three_users.xml'); } static public function write_sequence_data() diff --git a/tests/test_framework/phpbb_database_test_connection_manager.php b/tests/test_framework/phpbb_database_test_connection_manager.php index 97281a0812..d7c2804aa7 100644 --- a/tests/test_framework/phpbb_database_test_connection_manager.php +++ b/tests/test_framework/phpbb_database_test_connection_manager.php @@ -430,17 +430,17 @@ class phpbb_database_test_connection_manager /** * Performs synchronisations on the database after a fixture has been loaded * - * @param PHPUnit_Extensions_Database_DataSet_XmlDataSet $tables Tables contained within the loaded fixture + * @param PHPUnit_Extensions_Database_DataSet_XmlDataSet $xml_data_set Information about the tables contained within the loaded fixture * * @return null */ - public function post_setup_synchronisation($xmlDataSet) + public function post_setup_synchronisation($xml_data_set) { $this->ensure_connected(__METHOD__); $queries = array(); // Get escaped versions of the table names used in the fixture - $table_names = array_map(array($this->pdo, 'PDO::quote'), $xmlDataSet->getTableNames()); + $table_names = array_map(array($this->pdo, 'PDO::quote'), $xml_data_set->getTableNames()); switch ($this->config['dbms']) { @@ -469,18 +469,20 @@ class phpbb_database_test_connection_manager continue; } - $maxval = (int)$max_row['MAX']; - $maxval++; + $max_val = (int) $max_row['MAX']; + $max_val++; - // This is not the "proper" way, but the proper way does not allow you to completely reset - // tables with no rows since you have to select the next value to make the change go into effct. - // You would have to go past the minimum value to set it correctly, but that's illegal. - // Since we have no objects attached to our sequencers (triggers aren't attached), this works fine. + /** + * This is not the "proper" way, but the proper way does not allow you to completely reset + * tables with no rows since you have to select the next value to make the change go into effect. + * You would have to go past the minimum value to set it correctly, but that's illegal. + * Since we have no objects attached to our sequencers (triggers aren't attached), this works fine. + */ $queries[] = 'DROP SEQUENCE ' . $row['SEQUENCE_NAME']; $queries[] = "CREATE SEQUENCE {$row['SEQUENCE_NAME']} MINVALUE {$row['MIN_VALUE']} INCREMENT BY {$row['INCREMENT_BY']} - START WITH $maxval"; + START WITH $max_val"; } break; @@ -495,7 +497,7 @@ class phpbb_database_test_connection_manager while ($row = $result->fetch(PDO::FETCH_ASSOC)) { // Get the columns used in the fixture for this table - $column_names = $xmlDataSet->getTableMetaData($row['table_name'])->getColumns(); + $column_names = $xml_data_set->getTableMetaData($row['table_name'])->getColumns(); // Skip sequences that weren't specified in the fixture if (!in_array($row['column_name'], $column_names)) From 4103c99a8676653a868014a6f58a76e8986bd5ed Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 1 Mar 2012 16:15:11 +0100 Subject: [PATCH 1140/1142] [ticket/10679] Add new permission for changing profile field information The setting is copied from "Can use signature" PHPBB3-10679 --- phpBB/includes/ucp/ucp_profile.php | 5 +++ phpBB/install/database_update.php | 48 +++++++++++++++++++-- phpBB/install/schemas/schema_data.sql | 3 +- phpBB/language/en/acp/permissions_phpbb.php | 1 + phpBB/language/en/ucp.php | 1 + phpBB/ucp.php | 6 +++ 6 files changed, 59 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index 89bf20a30f..e7cea06a45 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -251,6 +251,11 @@ class ucp_profile break; case 'profile_info': + // Do not display profile information panel if not authed to do so + if (!$auth->acl_get('u_chgprofileinfo')) + { + trigger_error('NO_AUTH_PROFILEINFO'); + } include($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index e966756337..f0a16844e9 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2731,8 +2731,6 @@ function change_database_data(&$no_updates, $version) $config->set('display_last_subject', '1'); } - $no_updates = false; - if (!isset($config['assets_version'])) { $config->set('assets_version', '1'); @@ -2771,7 +2769,7 @@ function change_database_data(&$no_updates, $version) } // PHPBB3-10601: Make inbox default. Add basename to ucp's pm category - + // Get the category wanted while checking, at the same time, if this has already been applied $sql = 'SELECT module_id, module_basename FROM ' . MODULES_TABLE . " @@ -2788,10 +2786,52 @@ function change_database_data(&$no_updates, $version) SET module_basename = 'ucp_pm' WHERE module_id = " . (int) $row['module_id']; - _sql($sql, $errored, $error_ary); + _sql($sql, $errored, $error_ary); } $db->sql_freeresult($result); + // Add new permission u_chgprofileinfo and duplicate settings from u_sig + include_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx); + $auth_admin = new auth_admin(); + + // Only add the new permission if it does not already exist + if (empty($auth_admin->acl_options['id']['u_chgprofileinfo'])) + { + $auth_admin->acl_add_option(array('global' => array('u_chgprofileinfo'))); + + // Now the tricky part, filling the permission + $old_id = $auth_admin->acl_options['id']['u_sig']; + $new_id = $auth_admin->acl_options['id']['u_chgprofileinfo']; + + $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE); + + foreach ($tables as $table) + { + $sql = 'SELECT * + FROM ' . $table . ' + WHERE auth_option_id = ' . $old_id; + $result = _sql($sql, $errored, $error_ary); + + $sql_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $row['auth_option_id'] = $new_id; + $sql_ary[] = $row; + } + $db->sql_freeresult($result); + + if (sizeof($sql_ary)) + { + $db->sql_multi_insert($table, $sql_ary); + } + } + + // Remove any old permission entries + $auth_admin->acl_clear_prefetch(); + } + + $no_updates = false; + break; } } diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index dbb5fd7481..7c1a7d40f5 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -387,6 +387,7 @@ INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgemail', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chggrp', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgname', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgpasswd', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgprofileinfo', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_download', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_hideonline', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_ignoreflood', 1); @@ -548,7 +549,7 @@ INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 22, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option NOT IN ('f_announce', 'f_attach', 'f_bump', 'f_delete', 'f_flash', 'f_icons', 'f_ignoreflood', 'f_sticky', 'f_user_lock', 'f_votechg'); # New Member (u_) -INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 23, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group'); +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 23, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group', 'u_chgprofileinfo'); # New Member (f_) INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 24, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove'); diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index b142cfd9aa..27ef714f8b 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -102,6 +102,7 @@ $lang = array_merge($lang, array( 'acl_u_chgemail' => array('lang' => 'Can change email address', 'cat' => 'profile'), 'acl_u_chgavatar' => array('lang' => 'Can change avatar', 'cat' => 'profile'), 'acl_u_chggrp' => array('lang' => 'Can change default usergroup', 'cat' => 'profile'), + 'acl_u_chgprofileinfo' => array('lang' => 'Can change profile field information', 'cat' => 'profile'), 'acl_u_attach' => array('lang' => 'Can attach files', 'cat' => 'post'), 'acl_u_download' => array('lang' => 'Can download files', 'cat' => 'post'), diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index b919699ea0..267ae00710 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -318,6 +318,7 @@ $lang = array_merge($lang, array( 'NO_AUTH_FORWARD_MESSAGE' => 'You are not authorised to forward private messages.', 'NO_AUTH_GROUP_MESSAGE' => 'You are not authorised to send private messages to groups.', 'NO_AUTH_PASSWORD_REMINDER' => 'You are not authorised to request a new password.', + 'NO_AUTH_PROFILEINFO' => 'You are not authorised to change your profile information.', 'NO_AUTH_READ_HOLD_MESSAGE' => 'You are not authorised to read private messages that are on hold.', 'NO_AUTH_READ_MESSAGE' => 'You are not authorised to read private messages.', 'NO_AUTH_READ_REMOVED_MESSAGE' => 'You are not able to read this message because it was removed by the author.', diff --git a/phpBB/ucp.php b/phpBB/ucp.php index a7e75f76c4..7f4cd94f6f 100644 --- a/phpBB/ucp.php +++ b/phpBB/ucp.php @@ -334,6 +334,12 @@ if (!$config['allow_topic_notify'] && !$config['allow_forum_notify']) $vars = array('module', 'id', 'mode'); extract($phpbb_dispatcher->trigger_event('core.ucp_display_module_before', compact($vars))); +// Do not display profile information panel if not authed to do so +if (!$auth->acl_get('u_chgprofileinfo')) +{ + $module->set_display('profile', 'profile_info', false); +} + // Select the active module $module->set_active($id, $mode); From 2f490293e4b8d08d6c1008ef5ea324ae259d9993 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 6 Dec 2012 16:33:12 +0100 Subject: [PATCH 1141/1142] [ticket/10679] Use module_auth to limit access to the module PHPBB3-10679 --- phpBB/includes/ucp/info/ucp_profile.php | 2 +- phpBB/install/database_update.php | 8 ++++++++ phpBB/ucp.php | 6 ------ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/phpBB/includes/ucp/info/ucp_profile.php b/phpBB/includes/ucp/info/ucp_profile.php index 201216e9fd..3581a7f533 100644 --- a/phpBB/includes/ucp/info/ucp_profile.php +++ b/phpBB/includes/ucp/info/ucp_profile.php @@ -19,7 +19,7 @@ class ucp_profile_info 'title' => 'UCP_PROFILE', 'version' => '1.0.0', 'modes' => array( - 'profile_info' => array('title' => 'UCP_PROFILE_PROFILE_INFO', 'auth' => '', 'cat' => array('UCP_PROFILE')), + 'profile_info' => array('title' => 'UCP_PROFILE_PROFILE_INFO', 'auth' => 'acl_u_chgprofileinfo', 'cat' => array('UCP_PROFILE')), 'signature' => array('title' => 'UCP_PROFILE_SIGNATURE', 'auth' => 'acl_u_sig', 'cat' => array('UCP_PROFILE')), 'avatar' => array('title' => 'UCP_PROFILE_AVATAR', 'auth' => 'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)', 'cat' => array('UCP_PROFILE')), 'reg_details' => array('title' => 'UCP_PROFILE_REG_DETAILS', 'auth' => '', 'cat' => array('UCP_PROFILE')), diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index f0a16844e9..95fd1ca2c2 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2830,6 +2830,14 @@ function change_database_data(&$no_updates, $version) $auth_admin->acl_clear_prefetch(); } + // Update the auth setting for the module + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'acl_u_chgprofileinfo' + WHERE module_class = 'ucp' + AND module_basename = 'profile' + AND module_mode = 'profile_info'"; + _sql($sql, $errored, $error_ary); + $no_updates = false; break; diff --git a/phpBB/ucp.php b/phpBB/ucp.php index 7f4cd94f6f..a7e75f76c4 100644 --- a/phpBB/ucp.php +++ b/phpBB/ucp.php @@ -334,12 +334,6 @@ if (!$config['allow_topic_notify'] && !$config['allow_forum_notify']) $vars = array('module', 'id', 'mode'); extract($phpbb_dispatcher->trigger_event('core.ucp_display_module_before', compact($vars))); -// Do not display profile information panel if not authed to do so -if (!$auth->acl_get('u_chgprofileinfo')) -{ - $module->set_display('profile', 'profile_info', false); -} - // Select the active module $module->set_active($id, $mode); From c23d2457e9be616bfa83aebc5e743130b6c69624 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 7 Dec 2012 12:50:21 +0100 Subject: [PATCH 1142/1142] [ticket/10679] Update module basename, we added the xcp_ prefix in 3.1 PHPBB3-10679 --- phpBB/install/database_update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 95fd1ca2c2..30592b995d 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2834,7 +2834,7 @@ function change_database_data(&$no_updates, $version) $sql = 'UPDATE ' . MODULES_TABLE . " SET module_auth = 'acl_u_chgprofileinfo' WHERE module_class = 'ucp' - AND module_basename = 'profile' + AND module_basename = 'ucp_profile' AND module_mode = 'profile_info'"; _sql($sql, $errored, $error_ary);