aboutsummaryrefslogtreecommitdiffstats
path: root/phpBB/includes
diff options
context:
space:
mode:
Diffstat (limited to 'phpBB/includes')
-rw-r--r--phpBB/includes/acp/acp_board.php5
-rw-r--r--phpBB/includes/acp/acp_extensions.php28
-rw-r--r--phpBB/includes/acp/acp_forums.php6
-rw-r--r--phpBB/includes/acp/acp_groups.php56
-rw-r--r--phpBB/includes/acp/acp_main.php18
-rw-r--r--phpBB/includes/acp/acp_modules.php16
-rw-r--r--phpBB/includes/acp/acp_permissions.php27
-rw-r--r--phpBB/includes/acp/acp_ranks.php14
-rw-r--r--phpBB/includes/acp/acp_styles.php4
-rw-r--r--phpBB/includes/acp/info/acp_extensions.php4
-rw-r--r--phpBB/includes/auth/auth.php22
-rw-r--r--phpBB/includes/cache/driver/file.php6
-rw-r--r--phpBB/includes/cache/driver/interface.php3
-rw-r--r--phpBB/includes/cache/driver/memory.php6
-rw-r--r--phpBB/includes/cache/driver/null.php4
-rw-r--r--phpBB/includes/cache/service.php133
-rw-r--r--phpBB/includes/captcha/captcha_non_gd.php2
-rw-r--r--phpBB/includes/constants.php4
-rw-r--r--phpBB/includes/datetime.php2
-rw-r--r--phpBB/includes/db/db_tools.php31
-rw-r--r--phpBB/includes/db/driver/driver.php8
-rw-r--r--phpBB/includes/db/driver/firebird.php10
-rw-r--r--phpBB/includes/db/driver/mssql.php12
-rw-r--r--phpBB/includes/db/driver/mssql_odbc.php10
-rw-r--r--phpBB/includes/db/driver/mssqlnative.php4
-rw-r--r--phpBB/includes/db/driver/mysql.php12
-rw-r--r--phpBB/includes/db/driver/mysqli.php12
-rw-r--r--phpBB/includes/db/driver/oracle.php12
-rw-r--r--phpBB/includes/db/driver/postgres.php12
-rw-r--r--phpBB/includes/db/driver/sqlite.php12
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_1.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_10.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_10_rc1.php30
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_10_rc2.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_10_rc3.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_11.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_11_rc1.php95
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_11_rc2.php50
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_12_rc1.php123
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_1_rc1.php108
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_2.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_2_rc1.php32
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_2_rc2.php80
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_3.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_3_rc1.php83
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_4.php49
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_4_rc1.php123
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_5.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_5_rc1.php124
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php42
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_6.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_6_rc1.php324
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_6_rc2.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_6_rc3.php40
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_6_rc4.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_7.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_7_pl1.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_7_rc1.php76
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_7_rc2.php73
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_8.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_8_rc1.php221
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_9.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_9_rc1.php124
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_9_rc2.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_9_rc3.php28
-rw-r--r--phpBB/includes/db/migration/data/30x/3_0_9_rc4.php28
-rw-r--r--phpBB/includes/db/migration/data/310/dev.php405
-rw-r--r--phpBB/includes/db/migration/data/310/extensions.php69
-rw-r--r--phpBB/includes/db/migration/data/310/notifications.php160
-rw-r--r--phpBB/includes/db/migration/data/310/reported_posts_display.php47
-rw-r--r--phpBB/includes/db/migration/data/310/style_update_p1.php157
-rw-r--r--phpBB/includes/db/migration/data/310/style_update_p2.php129
-rw-r--r--phpBB/includes/db/migration/data/310/timezone.php (renamed from phpBB/includes/update_helpers.php)65
-rw-r--r--phpBB/includes/db/migration/data/310/timezone_p2.php43
-rw-r--r--phpBB/includes/db/migration/exception.php79
-rw-r--r--phpBB/includes/db/migration/migration.php190
-rw-r--r--phpBB/includes/db/migration/tool/config.php150
-rw-r--r--phpBB/includes/db/migration/tool/interface.php33
-rw-r--r--phpBB/includes/db/migration/tool/module.php502
-rw-r--r--phpBB/includes/db/migration/tool/permission.php622
-rw-r--r--phpBB/includes/db/migrator.php757
-rw-r--r--phpBB/includes/di/extension/config.php1
-rw-r--r--phpBB/includes/extension/base.php15
-rw-r--r--phpBB/includes/extension/finder.php90
-rw-r--r--phpBB/includes/extension/interface.php4
-rw-r--r--phpBB/includes/extension/manager.php90
-rw-r--r--phpBB/includes/functions.php112
-rw-r--r--phpBB/includes/functions_acp.php27
-rw-r--r--phpBB/includes/functions_admin.php76
-rw-r--r--phpBB/includes/functions_container.php57
-rw-r--r--phpBB/includes/functions_install.php5
-rw-r--r--phpBB/includes/functions_messenger.php24
-rw-r--r--phpBB/includes/functions_posting.php308
-rw-r--r--phpBB/includes/functions_privmsgs.php116
-rw-r--r--phpBB/includes/functions_user.php111
-rw-r--r--phpBB/includes/hook/finder.php84
-rw-r--r--phpBB/includes/mcp/mcp_pm_reports.php7
-rw-r--r--phpBB/includes/mcp/mcp_queue.php12
-rw-r--r--phpBB/includes/mcp/mcp_reports.php66
-rw-r--r--phpBB/includes/notification/manager.php853
-rw-r--r--phpBB/includes/notification/method/base.php116
-rw-r--r--phpBB/includes/notification/method/email.php129
-rw-r--r--phpBB/includes/notification/method/interface.php48
-rw-r--r--phpBB/includes/notification/method/jabber.php77
-rw-r--r--phpBB/includes/notification/type/approve_post.php140
-rw-r--r--phpBB/includes/notification/type/approve_topic.php138
-rw-r--r--phpBB/includes/notification/type/base.php479
-rw-r--r--phpBB/includes/notification/type/bookmark.php137
-rw-r--r--phpBB/includes/notification/type/disapprove_post.php120
-rw-r--r--phpBB/includes/notification/type/disapprove_topic.php120
-rw-r--r--phpBB/includes/notification/type/interface.php189
-rw-r--r--phpBB/includes/notification/type/pm.php184
-rw-r--r--phpBB/includes/notification/type/post.php370
-rw-r--r--phpBB/includes/notification/type/post_in_queue.php137
-rw-r--r--phpBB/includes/notification/type/quote.php221
-rw-r--r--phpBB/includes/notification/type/report_pm.php229
-rw-r--r--phpBB/includes/notification/type/report_pm_closed.php155
-rw-r--r--phpBB/includes/notification/type/report_post.php196
-rw-r--r--phpBB/includes/notification/type/report_post_closed.php155
-rw-r--r--phpBB/includes/notification/type/topic.php277
-rw-r--r--phpBB/includes/notification/type/topic_in_queue.php130
-rw-r--r--phpBB/includes/search/base.php15
-rw-r--r--phpBB/includes/search/fulltext_mysql.php56
-rw-r--r--phpBB/includes/search/fulltext_native.php53
-rw-r--r--phpBB/includes/search/fulltext_postgres.php103
-rw-r--r--phpBB/includes/search/fulltext_sphinx.php27
-rw-r--r--phpBB/includes/session.php2
-rw-r--r--phpBB/includes/sphinxapi.php3424
-rw-r--r--phpBB/includes/style/extension_path_provider.php2
-rw-r--r--phpBB/includes/ucp/info/ucp_notifications.php35
-rw-r--r--phpBB/includes/ucp/ucp_activate.php2
-rw-r--r--phpBB/includes/ucp/ucp_groups.php8
-rw-r--r--phpBB/includes/ucp/ucp_notifications.php226
-rw-r--r--phpBB/includes/ucp/ucp_pm_compose.php2
-rw-r--r--phpBB/includes/ucp/ucp_prefs.php7
-rw-r--r--phpBB/includes/user_loader.php231
136 files changed, 13763 insertions, 2439 deletions
diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php
index 322e1c55d8..a213102cc6 100644
--- a/phpBB/includes/acp/acp_board.php
+++ b/phpBB/includes/acp/acp_board.php
@@ -314,6 +314,7 @@ class acp_board
'load_online_time' => array('lang' => 'ONLINE_LENGTH', 'validate' => 'int:0', 'type' => 'text:4:3', 'explain' => true, 'append' => ' ' . $user->lang['MINUTES']),
'legend2' => 'GENERAL_OPTIONS',
+ 'load_notifications' => array('lang' => 'LOAD_NOTIFICATIONS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true),
'load_db_track' => array('lang' => 'YES_POST_MARKING', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true),
'load_db_lastread' => array('lang' => 'YES_READ_MARKING', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true),
'load_anon_lastread' => array('lang' => 'YES_ANON_READ_MARKING', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true),
@@ -413,8 +414,8 @@ class acp_board
'board_email_form' => array('lang' => 'BOARD_EMAIL_FORM', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true),
'email_function_name' => array('lang' => 'EMAIL_FUNCTION_NAME', 'validate' => 'string', 'type' => 'text:20:50', 'explain' => true),
'email_package_size' => array('lang' => 'EMAIL_PACKAGE_SIZE', 'validate' => 'int:0', 'type' => 'text:5:5', 'explain' => true),
- 'board_contact' => array('lang' => 'CONTACT_EMAIL', 'validate' => 'string', 'type' => 'text:25:100', 'explain' => true),
- 'board_email' => array('lang' => 'ADMIN_EMAIL', 'validate' => 'string', 'type' => 'text:25:100', 'explain' => true),
+ 'board_contact' => array('lang' => 'CONTACT_EMAIL', 'validate' => 'email', 'type' => 'text:25:100', 'explain' => true),
+ 'board_email' => array('lang' => 'ADMIN_EMAIL', 'validate' => 'email', 'type' => 'text:25:100', 'explain' => true),
'board_email_sig' => array('lang' => 'EMAIL_SIG', 'validate' => 'string', 'type' => 'textarea:5:30', 'explain' => true),
'board_hide_emails' => array('lang' => 'BOARD_HIDE_EMAILS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true),
diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php
index a0bcf62ecc..24211196bd 100644
--- a/phpBB/includes/acp/acp_extensions.php
+++ b/phpBB/includes/acp/acp_extensions.php
@@ -37,7 +37,7 @@ class acp_extensions
$this->template = $template;
$this->user = $user;
- $user->add_lang(array('install', 'acp/extensions'));
+ $user->add_lang(array('install', 'acp/extensions', 'migrator'));
$this->page_title = 'ACP_EXTENSIONS';
@@ -103,11 +103,18 @@ class acp_extensions
trigger_error($user->lang['EXTENSION_NOT_AVAILABLE'] . adm_back_link($this->u_action));
}
- if ($phpbb_extension_manager->enable_step($ext_name))
+ try
{
- $template->assign_var('S_NEXT_STEP', true);
+ if ($phpbb_extension_manager->enable_step($ext_name))
+ {
+ $template->assign_var('S_NEXT_STEP', true);
- meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name));
+ meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name));
+ }
+ }
+ catch (phpbb_db_migration_exception $e)
+ {
+ $template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($user));
}
$this->tpl_name = 'acp_ext_enable';
@@ -156,11 +163,18 @@ class acp_extensions
break;
case 'purge':
- if ($phpbb_extension_manager->purge_step($ext_name))
+ try
{
- $template->assign_var('S_NEXT_STEP', true);
+ if ($phpbb_extension_manager->purge_step($ext_name))
+ {
+ $template->assign_var('S_NEXT_STEP', true);
- meta_refresh(0, $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name));
+ meta_refresh(0, $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name));
+ }
+ }
+ catch (phpbb_db_migration_exception $e)
+ {
+ $template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($user));
}
$this->tpl_name = 'acp_ext_purge';
diff --git a/phpBB/includes/acp/acp_forums.php b/phpBB/includes/acp/acp_forums.php
index e8f0a01b2e..970b033995 100644
--- a/phpBB/includes/acp/acp_forums.php
+++ b/phpBB/includes/acp/acp_forums.php
@@ -206,7 +206,7 @@ class acp_forums
($action != 'edit' || empty($forum_id) || ($auth->acl_get('a_fauth') && $auth->acl_get('a_authusers') && $auth->acl_get('a_authgroups') && $auth->acl_get('a_mauth'))))
{
copy_forum_permissions($forum_perm_from, $forum_data['forum_id'], ($action == 'edit') ? true : false);
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
$copied_permissions = true;
}
/* Commented out because of questionable UI workflow - re-visit for 3.0.7
@@ -266,7 +266,7 @@ class acp_forums
add_log('admin', 'LOG_FORUM_' . strtoupper($action), $row['forum_name'], $move_forum_name);
$cache->destroy('sql', FORUMS_TABLE);
}
-
+
if ($request->is_ajax())
{
$json_response = new phpbb_json_response;
@@ -767,7 +767,7 @@ class acp_forums
if (!empty($forum_perm_from) && $forum_perm_from != $forum_id)
{
copy_forum_permissions($forum_perm_from, $forum_id, true);
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
$auth->acl_clear_prefetch();
$cache->destroy('sql', FORUMS_TABLE);
diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php
index b604e20094..21b1d4b837 100644
--- a/phpBB/includes/acp/acp_groups.php
+++ b/phpBB/includes/acp/acp_groups.php
@@ -126,13 +126,34 @@ class acp_groups
{
trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING);
}
+ else if (empty($mark_ary))
+ {
+ trigger_error($user->lang['NO_USERS'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING);
+ }
if (confirm_box(true))
{
$group_name = ($group_row['group_type'] == GROUP_SPECIAL) ? $user->lang['G_' . $group_row['group_name']] : $group_row['group_name'];
+ group_user_attributes('default', $group_id, $mark_ary, false, $group_name, $group_row);
+ trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id));
+ }
+ else
+ {
+ confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array(
+ 'mark' => $mark_ary,
+ 'g' => $group_id,
+ 'i' => $id,
+ 'mode' => $mode,
+ 'action' => $action))
+ );
+ }
- if (!sizeof($mark_ary))
+ break;
+ case 'set_default_on_all':
+ if (confirm_box(true))
{
+ $group_name = ($group_row['group_type'] == GROUP_SPECIAL) ? $user->lang['G_' . $group_row['group_name']] : $group_row['group_name'];
+
$start = 0;
do
@@ -163,28 +184,25 @@ class acp_groups
$db->sql_freeresult($result);
}
while ($start);
+
+ trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id));
}
else
{
- group_user_attributes('default', $group_id, $mark_ary, false, $group_name, $group_row);
+ confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array(
+ 'mark' => $mark_ary,
+ 'g' => $group_id,
+ 'i' => $id,
+ 'mode' => $mode,
+ 'action' => $action))
+ );
}
-
- trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id));
- }
- else
- {
- confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array(
- 'mark' => $mark_ary,
- 'g' => $group_id,
- 'i' => $id,
- 'mode' => $mode,
- 'action' => $action))
- );
- }
-
break;
-
case 'deleteusers':
+ if (empty($mark_ary))
+ {
+ trigger_error($user->lang['NO_USERS'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING);
+ }
case 'delete':
if (!$group_id)
{
@@ -439,7 +457,7 @@ class acp_groups
foreach ($test_variables as $test => $type)
{
- if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || in_array($test, $set_attributes)))
+ if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || isset($group_attributes['group_avatar']) && strpos($test, 'avatar') === 0 || in_array($test, $set_attributes)))
{
settype($submit_ary[$test], $type);
$group_attributes['group_' . $test] = $group_row['group_' . $test] = $submit_ary[$test];
@@ -698,7 +716,7 @@ class acp_groups
'U_ACTION' => $this->u_action . "&g=$group_id",
'U_BACK' => $this->u_action,
'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=list&field=usernames'),
- 'U_DEFAULT_ALL' => "{$this->u_action}&action=default&g=$group_id",
+ 'U_DEFAULT_ALL' => "{$this->u_action}&action=set_default_on_all&g=$group_id",
));
// Grab the members
diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php
index 27813b1cc7..64c2b338fd 100644
--- a/phpBB/includes/acp/acp_main.php
+++ b/phpBB/includes/acp/acp_main.php
@@ -24,7 +24,7 @@ class acp_main
function main($id, $mode)
{
- global $config, $db, $user, $auth, $template, $request;
+ global $config, $db, $cache, $user, $auth, $template, $request;
global $phpbb_root_path, $phpbb_admin_path, $phpEx;
// Show restore permissions notice
@@ -129,7 +129,7 @@ class acp_main
set_config('record_online_users', 1, true);
set_config('record_online_date', time(), true);
add_log('admin', 'LOG_RESET_ONLINE');
-
+
if ($request->is_ajax())
{
trigger_error('RESET_ONLINE_SUCCESS');
@@ -184,7 +184,7 @@ class acp_main
update_last_username();
add_log('admin', 'LOG_RESYNC_STATS');
-
+
if ($request->is_ajax())
{
trigger_error('RESYNC_STATS_SUCCESS');
@@ -251,7 +251,7 @@ class acp_main
}
add_log('admin', 'LOG_RESYNC_POSTCOUNTS');
-
+
if ($request->is_ajax())
{
trigger_error('RESYNC_POSTCOUNTS_SUCCESS');
@@ -266,7 +266,7 @@ class acp_main
set_config('board_startdate', time() - 1);
add_log('admin', 'LOG_RESET_DATE');
-
+
if ($request->is_ajax())
{
trigger_error('RESET_DATE_SUCCESS');
@@ -346,7 +346,7 @@ class acp_main
}
add_log('admin', 'LOG_RESYNC_POST_MARKING');
-
+
if ($request->is_ajax())
{
trigger_error('RESYNC_POST_MARKING_SUCCESS');
@@ -359,10 +359,10 @@ class acp_main
// Clear permissions
$auth->acl_clear_prefetch();
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
add_log('admin', 'LOG_PURGE_CACHE');
-
+
if ($request->is_ajax())
{
trigger_error('PURGE_CACHE_SUCCESS');
@@ -413,7 +413,7 @@ class acp_main
$db->sql_query($sql);
add_log('admin', 'LOG_PURGE_SESSIONS');
-
+
if ($request->is_ajax())
{
trigger_error('PURGE_SESSIONS_SUCCESS');
diff --git a/phpBB/includes/acp/acp_modules.php b/phpBB/includes/acp/acp_modules.php
index 8528dc91c4..fce26bf45f 100644
--- a/phpBB/includes/acp/acp_modules.php
+++ b/phpBB/includes/acp/acp_modules.php
@@ -535,8 +535,14 @@ class acp_modules
/**
* Get available module information from module files
+ *
+ * @param string $module
+ * @param bool|string $module_class
+ * @param bool $use_all_available Use all available instead of just all
+ * enabled extensions
+ * @return array
*/
- function get_module_infos($module = '', $module_class = false)
+ function get_module_infos($module = '', $module_class = false, $use_all_available = false)
{
global $phpbb_root_path, $phpEx;
@@ -556,7 +562,7 @@ class acp_modules
->extension_directory("/$module_class")
->core_path("includes/$module_class/info/")
->core_prefix($module_class . '_')
- ->get_classes();
+ ->get_classes(true, $use_all_available);
foreach ($modules as $module)
{
@@ -740,15 +746,15 @@ class acp_modules
*/
function remove_cache_file()
{
- global $cache;
+ global $phpbb_container;
// Sanitise for future path use, it's escaped as appropriate for queries
$p_class = str_replace(array('.', '/', '\\'), '', basename($this->module_class));
- $cache->destroy('_modules_' . $p_class);
+ $phpbb_container->get('cache.driver')->destroy('_modules_' . $p_class);
// Additionally remove sql cache
- $cache->destroy('sql', MODULES_TABLE);
+ $phpbb_container->get('cache.driver')->destroy('sql', MODULES_TABLE);
}
/**
diff --git a/phpBB/includes/acp/acp_permissions.php b/phpBB/includes/acp/acp_permissions.php
index dd071074de..a64765f4f5 100644
--- a/phpBB/includes/acp/acp_permissions.php
+++ b/phpBB/includes/acp/acp_permissions.php
@@ -656,7 +656,7 @@ class acp_permissions
*/
function set_permissions($mode, $permission_type, &$auth_admin, &$user_id, &$group_id)
{
- global $user, $auth;
+ global $db, $cache, $user, $auth;
global $request;
$psubmit = request_var('psubmit', array(0 => array(0 => 0)));
@@ -726,13 +726,13 @@ class acp_permissions
// Do we need to recache the moderator lists?
if ($permission_type == 'm_')
{
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
}
// Remove users who are now moderators or admins from everyones foes list
if ($permission_type == 'm_' || $permission_type == 'a_')
{
- update_foes($group_id, $user_id);
+ phpbb_update_foes($db, $auth, $group_id, $user_id);
}
$this->log_action($mode, 'add', $permission_type, $ug_type, $ug_id, $forum_id);
@@ -745,7 +745,7 @@ class acp_permissions
*/
function set_all_permissions($mode, $permission_type, &$auth_admin, &$user_id, &$group_id)
{
- global $user, $auth;
+ global $db, $cache, $user, $auth;
global $request;
// User or group to be set?
@@ -794,13 +794,13 @@ class acp_permissions
// Do we need to recache the moderator lists?
if ($permission_type == 'm_')
{
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
}
// Remove users who are now moderators or admins from everyones foes list
if ($permission_type == 'm_' || $permission_type == 'a_')
{
- update_foes($group_id, $user_id);
+ phpbb_update_foes($db, $auth, $group_id, $user_id);
}
$this->log_action($mode, 'add', $permission_type, $ug_type, $ug_ids, $forum_ids);
@@ -858,7 +858,7 @@ class acp_permissions
*/
function remove_permissions($mode, $permission_type, &$auth_admin, &$user_id, &$group_id, &$forum_id)
{
- global $user, $db, $auth;
+ global $user, $db, $cache, $auth;
// User or group to be set?
$ug_type = (sizeof($user_id)) ? 'user' : 'group';
@@ -874,7 +874,7 @@ class acp_permissions
// Do we need to recache the moderator lists?
if ($permission_type == 'm_')
{
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
}
$this->log_action($mode, 'del', $permission_type, $ug_type, (($ug_type == 'user') ? $user_id : $group_id), (sizeof($forum_id) ? $forum_id : array(0 => 0)));
@@ -952,12 +952,7 @@ class acp_permissions
if ($user_id != $user->data['user_id'])
{
- $sql = 'SELECT user_id, username, user_permissions, user_type
- FROM ' . USERS_TABLE . '
- WHERE user_id = ' . $user_id;
- $result = $db->sql_query($sql);
- $userdata = $db->sql_fetchrow($result);
- $db->sql_freeresult($result);
+ $userdata = $auth->obtain_user_data($user_id);
}
else
{
@@ -1172,7 +1167,7 @@ class acp_permissions
*/
function copy_forum_permissions()
{
- global $auth, $cache, $template, $user;
+ global $db, $auth, $cache, $template, $user;
$user->add_lang('acp/forums');
@@ -1187,7 +1182,7 @@ class acp_permissions
{
if (copy_forum_permissions($src, $dest))
{
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
$auth->acl_clear_prefetch();
$cache->destroy('sql', FORUMS_TABLE);
diff --git a/phpBB/includes/acp/acp_ranks.php b/phpBB/includes/acp/acp_ranks.php
index d9ed5b17f1..6b06d03f52 100644
--- a/phpBB/includes/acp/acp_ranks.php
+++ b/phpBB/includes/acp/acp_ranks.php
@@ -71,7 +71,7 @@ class acp_ranks
'rank_min' => $min_posts,
'rank_image' => htmlspecialchars_decode($rank_image)
);
-
+
if ($rank_id)
{
$sql = 'UPDATE ' . RANKS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " WHERE rank_id = $rank_id";
@@ -122,7 +122,7 @@ class acp_ranks
$cache->destroy('_ranks');
add_log('admin', 'LOG_RANK_REMOVED', $rank_title);
-
+
if ($request->is_ajax())
{
$json_response = new phpbb_json_response;
@@ -151,7 +151,7 @@ class acp_ranks
case 'add':
$data = $ranks = $existing_imgs = array();
-
+
$sql = 'SELECT *
FROM ' . RANKS_TABLE . '
ORDER BY rank_min ASC, rank_special ASC';
@@ -209,17 +209,17 @@ class acp_ranks
'RANK_TITLE' => (isset($ranks['rank_title'])) ? $ranks['rank_title'] : '',
'S_FILENAME_LIST' => $filename_list,
- 'RANK_IMAGE' => ($edit_img) ? $phpbb_root_path . $config['ranks_path'] . '/' . $edit_img : $phpbb_admin_path . 'images/spacer.gif',
+ 'RANK_IMAGE' => ($edit_img) ? $phpbb_root_path . $config['ranks_path'] . '/' . $edit_img : htmlspecialchars($phpbb_admin_path) . 'images/spacer.gif',
'S_SPECIAL_RANK' => (isset($ranks['rank_special']) && $ranks['rank_special']) ? true : false,
'MIN_POSTS' => (isset($ranks['rank_min']) && !$ranks['rank_special']) ? $ranks['rank_min'] : 0)
);
-
+
return;
break;
}
-
+
$template->assign_vars(array(
'U_ACTION' => $this->u_action)
);
@@ -241,7 +241,7 @@ class acp_ranks
'U_EDIT' => $this->u_action . '&action=edit&id=' . $row['rank_id'],
'U_DELETE' => $this->u_action . '&action=delete&id=' . $row['rank_id'])
- );
+ );
}
$db->sql_freeresult($result);
diff --git a/phpBB/includes/acp/acp_styles.php b/phpBB/includes/acp/acp_styles.php
index db77825ae7..266495972b 100644
--- a/phpBB/includes/acp/acp_styles.php
+++ b/phpBB/includes/acp/acp_styles.php
@@ -137,11 +137,13 @@ class acp_styles
*/
protected function action_cache()
{
+ global $db, $cache, $auth;
+
$this->cache->purge();
// Clear permissions
$this->auth->acl_clear_prefetch();
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
add_log('admin', 'LOG_PURGE_CACHE');
diff --git a/phpBB/includes/acp/info/acp_extensions.php b/phpBB/includes/acp/info/acp_extensions.php
index 03d7059165..174b365af0 100644
--- a/phpBB/includes/acp/info/acp_extensions.php
+++ b/phpBB/includes/acp/info/acp_extensions.php
@@ -16,10 +16,10 @@ class acp_extensions_info
{
return array(
'filename' => 'acp_extensions',
- 'title' => 'ACP_EXTENSIONS_MANAGEMENT',
+ 'title' => 'ACP_EXTENSION_MANAGEMENT',
'version' => '1.0.0',
'modes' => array(
- 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_EXTENSIONS_MANAGEMENT')),
+ 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_EXTENSION_MANAGEMENT')),
),
);
}
diff --git a/phpBB/includes/auth/auth.php b/phpBB/includes/auth/auth.php
index e3bccaf47b..2535247571 100644
--- a/phpBB/includes/auth/auth.php
+++ b/phpBB/includes/auth/auth.php
@@ -103,6 +103,26 @@ class phpbb_auth
}
/**
+ * Retrieves data wanted by acl function from the database for the
+ * specified user.
+ *
+ * @param int $user_id User ID
+ * @return array User attributes
+ */
+ public function obtain_user_data($user_id)
+ {
+ global $db;
+
+ $sql = 'SELECT user_id, username, user_permissions, user_type
+ FROM ' . USERS_TABLE . '
+ WHERE user_id = ' . $user_id;
+ $result = $db->sql_query($sql);
+ $user_data = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+ return $user_data;
+ }
+
+ /**
* Fill ACL array with relevant bitstrings from user_permissions column
* @access private
*/
@@ -191,7 +211,7 @@ class phpbb_auth
/**
* Get forums with the specified permission setting
- * if the option is prefixed with !, then the result becomes nagated
+ * if the option is prefixed with !, then the result becomes negated
*
* @param bool $clean set to true if only values needs to be returned which are set/unset
*/
diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php
index 691abe0438..85decbe3e8 100644
--- a/phpBB/includes/cache/driver/file.php
+++ b/phpBB/includes/cache/driver/file.php
@@ -367,12 +367,10 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base
}
/**
- * Save sql query
+ * {@inheritDoc}
*/
- function sql_save($query, $query_result, $ttl)
+ function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl)
{
- global $db;
-
// Remove extra spaces and tabs
$query = preg_replace('/[\n\r\s\t]+/', ' ', $query);
diff --git a/phpBB/includes/cache/driver/interface.php b/phpBB/includes/cache/driver/interface.php
index d403bbcd71..53f684d1c8 100644
--- a/phpBB/includes/cache/driver/interface.php
+++ b/phpBB/includes/cache/driver/interface.php
@@ -85,6 +85,7 @@ interface phpbb_cache_driver_interface
* result to persistent storage. In other words, there is no need
* to call save() afterwards.
*
+ * @param phpbb_db_driver $db Database connection
* @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
* dbal::sql_fetchrow to get all rows and store them
@@ -95,7 +96,7 @@ interface phpbb_cache_driver_interface
* representing the query should be returned. Otherwise
* the original $query_result should be returned.
*/
- public function sql_save($query, $query_result, $ttl);
+ public function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl);
/**
* Check if result for a given SQL query exists in cache.
diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php
index c39f9f7850..f77a1df316 100644
--- a/phpBB/includes/cache/driver/memory.php
+++ b/phpBB/includes/cache/driver/memory.php
@@ -283,12 +283,10 @@ abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base
}
/**
- * Save sql query
+ * {@inheritDoc}
*/
- function sql_save($query, $query_result, $ttl)
+ function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl)
{
- global $db;
-
// Remove extra spaces and tabs
$query = preg_replace('/[\n\r\s\t]+/', ' ', $query);
$hash = md5($query);
diff --git a/phpBB/includes/cache/driver/null.php b/phpBB/includes/cache/driver/null.php
index 687604d14f..2fadc27ba3 100644
--- a/phpBB/includes/cache/driver/null.php
+++ b/phpBB/includes/cache/driver/null.php
@@ -105,9 +105,9 @@ class phpbb_cache_driver_null extends phpbb_cache_driver_base
}
/**
- * Save sql query
+ * {@inheritDoc}
*/
- function sql_save($query, $query_result, $ttl)
+ function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl)
{
return $query_result;
}
diff --git a/phpBB/includes/cache/service.php b/phpBB/includes/cache/service.php
index e63ec6e33a..69c5e0fdd0 100644
--- a/phpBB/includes/cache/service.php
+++ b/phpBB/includes/cache/service.php
@@ -21,16 +21,57 @@ if (!defined('IN_PHPBB'))
*/
class phpbb_cache_service
{
- private $driver;
+ /**
+ * Cache driver.
+ *
+ * @var phpbb_cache_driver_interface
+ */
+ protected $driver;
+
+ /**
+ * The config.
+ *
+ * @var phpbb_config
+ */
+ protected $config;
+
+ /**
+ * Database connection.
+ *
+ * @var phpbb_db_driver
+ */
+ protected $db;
+
+ /**
+ * Root path.
+ *
+ * @var string
+ */
+ protected $phpbb_root_path;
+
+ /**
+ * PHP extension.
+ *
+ * @var string
+ */
+ protected $php_ext;
/**
* Creates a cache service around a cache driver
*
* @param phpbb_cache_driver_interface $driver The cache driver
+ * @param phpbb_config $config The config
+ * @param phpbb_db_driver $db Database connection
+ * @param string $phpbb_root_path Root path
+ * @param string $php_ext PHP extension
*/
- public function __construct(phpbb_cache_driver_interface $driver = null)
+ public function __construct(phpbb_cache_driver_interface $driver, phpbb_config $config, phpbb_db_driver $db, $phpbb_root_path, $php_ext)
{
$this->set_driver($driver);
+ $this->config = $config;
+ $this->db = $db;
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
}
/**
@@ -64,21 +105,19 @@ class phpbb_cache_service
*/
function obtain_word_list()
{
- global $db;
-
if (($censors = $this->driver->get('_word_censors')) === false)
{
$sql = 'SELECT word, replacement
FROM ' . WORDS_TABLE;
- $result = $db->sql_query($sql);
+ $result = $this->db->sql_query($sql);
$censors = array();
- while ($row = $db->sql_fetchrow($result))
+ while ($row = $this->db->sql_fetchrow($result))
{
$censors['match'][] = get_censor_preg_expression($row['word']);
$censors['replace'][] = $row['replacement'];
}
- $db->sql_freeresult($result);
+ $this->db->sql_freeresult($result);
$this->driver->put('_word_censors', $censors);
}
@@ -93,23 +132,21 @@ class phpbb_cache_service
{
if (($icons = $this->driver->get('_icons')) === false)
{
- global $db;
-
// Topic icons
$sql = 'SELECT *
FROM ' . ICONS_TABLE . '
ORDER BY icons_order';
- $result = $db->sql_query($sql);
+ $result = $this->db->sql_query($sql);
$icons = array();
- while ($row = $db->sql_fetchrow($result))
+ while ($row = $this->db->sql_fetchrow($result))
{
$icons[$row['icons_id']]['img'] = $row['icons_url'];
$icons[$row['icons_id']]['width'] = (int) $row['icons_width'];
$icons[$row['icons_id']]['height'] = (int) $row['icons_height'];
$icons[$row['icons_id']]['display'] = (bool) $row['display_on_posting'];
}
- $db->sql_freeresult($result);
+ $this->db->sql_freeresult($result);
$this->driver->put('_icons', $icons);
}
@@ -124,15 +161,13 @@ class phpbb_cache_service
{
if (($ranks = $this->driver->get('_ranks')) === false)
{
- global $db;
-
$sql = 'SELECT *
FROM ' . RANKS_TABLE . '
ORDER BY rank_min DESC';
- $result = $db->sql_query($sql);
+ $result = $this->db->sql_query($sql);
$ranks = array();
- while ($row = $db->sql_fetchrow($result))
+ while ($row = $this->db->sql_fetchrow($result))
{
if ($row['rank_special'])
{
@@ -150,7 +185,7 @@ class phpbb_cache_service
);
}
}
- $db->sql_freeresult($result);
+ $this->db->sql_freeresult($result);
$this->driver->put('_ranks', $ranks);
}
@@ -169,8 +204,6 @@ class phpbb_cache_service
{
if (($extensions = $this->driver->get('_extensions')) === false)
{
- global $db;
-
$extensions = array(
'_allowed_post' => array(),
'_allowed_pm' => array(),
@@ -181,9 +214,9 @@ class phpbb_cache_service
FROM ' . EXTENSIONS_TABLE . ' e, ' . EXTENSION_GROUPS_TABLE . ' g
WHERE e.group_id = g.group_id
AND (g.allow_group = 1 OR g.allow_in_pm = 1)';
- $result = $db->sql_query($sql);
+ $result = $this->db->sql_query($sql);
- while ($row = $db->sql_fetchrow($result))
+ while ($row = $this->db->sql_fetchrow($result))
{
$extension = strtolower(trim($row['extension']));
@@ -210,7 +243,7 @@ class phpbb_cache_service
$extensions['_allowed_pm'][$extension] = 0;
}
}
- $db->sql_freeresult($result);
+ $this->db->sql_freeresult($result);
$this->driver->put('_extensions', $extensions);
}
@@ -275,9 +308,7 @@ class phpbb_cache_service
{
if (($bots = $this->driver->get('_bots')) === false)
{
- global $db;
-
- switch ($db->sql_layer)
+ switch ($this->db->sql_layer)
{
case 'mssql':
case 'mssql_odbc':
@@ -303,14 +334,14 @@ class phpbb_cache_service
ORDER BY LENGTH(bot_agent) DESC';
break;
}
- $result = $db->sql_query($sql);
+ $result = $this->db->sql_query($sql);
$bots = array();
- while ($row = $db->sql_fetchrow($result))
+ while ($row = $this->db->sql_fetchrow($result))
{
$bots[] = $row;
}
- $db->sql_freeresult($result);
+ $this->db->sql_freeresult($result);
$this->driver->put('_bots', $bots);
}
@@ -323,8 +354,6 @@ class phpbb_cache_service
*/
function obtain_cfg_items($style)
{
- global $config, $phpbb_root_path;
-
$parsed_array = $this->driver->get('_cfg_' . $style['style_path']);
if ($parsed_array === false)
@@ -332,14 +361,14 @@ class phpbb_cache_service
$parsed_array = array();
}
- $filename = $phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg';
+ $filename = $this->phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg';
if (!file_exists($filename))
{
return $parsed_array;
}
- if (!isset($parsed_array['filetime']) || (($config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime'])))
+ if (!isset($parsed_array['filetime']) || (($this->config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime'])))
{
// Re-parse cfg file
$parsed_array = parse_cfg_file($filename);
@@ -358,54 +387,20 @@ class phpbb_cache_service
{
if (($usernames = $this->driver->get('_disallowed_usernames')) === false)
{
- global $db;
-
$sql = 'SELECT disallow_username
FROM ' . DISALLOW_TABLE;
- $result = $db->sql_query($sql);
+ $result = $this->db->sql_query($sql);
$usernames = array();
- while ($row = $db->sql_fetchrow($result))
+ while ($row = $this->db->sql_fetchrow($result))
{
$usernames[] = str_replace('%', '.*?', preg_quote(utf8_clean_string($row['disallow_username']), '#'));
}
- $db->sql_freeresult($result);
+ $this->db->sql_freeresult($result);
$this->driver->put('_disallowed_usernames', $usernames);
}
return $usernames;
}
-
- /**
- * Obtain hooks...
- */
- function obtain_hooks()
- {
- global $phpbb_root_path, $phpEx;
-
- if (($hook_files = $this->driver->get('_hooks')) === false)
- {
- $hook_files = array();
-
- // Now search for hooks...
- $dh = @opendir($phpbb_root_path . 'includes/hooks/');
-
- if ($dh)
- {
- while (($file = readdir($dh)) !== false)
- {
- if (strpos($file, 'hook_') === 0 && substr($file, -(strlen($phpEx) + 1)) === '.' . $phpEx)
- {
- $hook_files[] = substr($file, 0, -(strlen($phpEx) + 1));
- }
- }
- closedir($dh);
- }
-
- $this->driver->put('_hooks', $hook_files);
- }
-
- return $hook_files;
- }
}
diff --git a/phpBB/includes/captcha/captcha_non_gd.php b/phpBB/includes/captcha/captcha_non_gd.php
index c2b97423e6..bb5067cafa 100644
--- a/phpBB/includes/captcha/captcha_non_gd.php
+++ b/phpBB/includes/captcha/captcha_non_gd.php
@@ -118,7 +118,7 @@ class captcha
$new_line = '';
$end = strlen($scanline) - ceil($width/2);
- for ($i = floor($width/2); $i < $end; $i++)
+ for ($i = (int) floor($width / 2); $i < $end; $i++)
{
$pixel = ord($scanline{$i});
diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php
index 62c06dc1d0..d0deb36a89 100644
--- a/phpBB/includes/constants.php
+++ b/phpBB/includes/constants.php
@@ -241,8 +241,11 @@ define('ICONS_TABLE', $table_prefix . 'icons');
define('LANG_TABLE', $table_prefix . 'lang');
define('LOG_TABLE', $table_prefix . 'log');
define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts');
+define('MIGRATIONS_TABLE', $table_prefix . 'migrations');
define('MODERATOR_CACHE_TABLE', $table_prefix . 'moderator_cache');
define('MODULES_TABLE', $table_prefix . 'modules');
+define('NOTIFICATION_TYPES_TABLE', $table_prefix . 'notification_types');
+define('NOTIFICATIONS_TABLE', $table_prefix . 'notifications');
define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options');
define('POLL_VOTES_TABLE', $table_prefix . 'poll_votes');
define('POSTS_TABLE', $table_prefix . 'posts');
@@ -276,6 +279,7 @@ define('TOPICS_POSTED_TABLE', $table_prefix . 'topics_posted');
define('TOPICS_TRACK_TABLE', $table_prefix . 'topics_track');
define('TOPICS_WATCH_TABLE', $table_prefix . 'topics_watch');
define('USER_GROUP_TABLE', $table_prefix . 'user_group');
+define('USER_NOTIFICATIONS_TABLE', $table_prefix . 'user_notifications');
define('USERS_TABLE', $table_prefix . 'users');
define('WARNINGS_TABLE', $table_prefix . 'warnings');
define('WORDS_TABLE', $table_prefix . 'words');
diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php
index b3462ddf67..3c6d4971b9 100644
--- a/phpBB/includes/datetime.php
+++ b/phpBB/includes/datetime.php
@@ -143,7 +143,7 @@ class phpbb_datetime extends DateTime
'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'],
+ 'lang' => array_filter($user->lang['datetime'], 'is_string'),
);
// Short representation of month in format? Some languages use different terms for the long and short format of May
diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php
index 2bb016cebd..983cdc18ea 100644
--- a/phpBB/includes/db/db_tools.php
+++ b/phpBB/includes/db/db_tools.php
@@ -303,7 +303,7 @@ class phpbb_db_tools
* @param phpbb_db_driver $db Database connection
* @param bool $return_statements True if only statements should be returned and no SQL being executed
*/
- function phpbb_db_tools(&$db, $return_statements = false)
+ function phpbb_db_tools(phpbb_db_driver $db, $return_statements = false)
{
$this->db = $db;
$this->return_statements = $return_statements;
@@ -346,6 +346,17 @@ class phpbb_db_tools
}
/**
+ * Setter for {@link $return_statements return_statements}.
+ *
+ * @param bool $return_statements True if SQL should not be executed but returned as strings
+ * @return null
+ */
+ public function set_return_statements($return_statements)
+ {
+ $this->return_statements = $return_statements;
+ }
+
+ /**
* Gets a list of tables in the database.
*
* @return array Array of table names (all lower case)
@@ -674,6 +685,8 @@ class phpbb_db_tools
* Handle passed database update array.
* Expected structure...
* Key being one of the following
+ * drop_tables: Drop tables
+ * add_tables: Add tables
* change_columns: Column changes (only type, not name)
* add_columns: Add columns to a table
* drop_keys: Dropping keys
@@ -1817,6 +1830,22 @@ class phpbb_db_tools
case 'mssql':
case 'mssqlnative':
+ // remove default cosntraints first
+ // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
+ $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000)
+ SET @drop_default_name =
+ (SELECT so.name FROM sysobjects so
+ JOIN sysconstraints sc ON so.id = sc.constid
+ WHERE object_name(so.parent_obj) = '{$table_name}'
+ AND so.xtype = 'D'
+ AND sc.colid = (SELECT colid FROM syscolumns
+ WHERE id = object_id('{$table_name}')
+ AND name = '{$column_name}'))
+ IF @drop_default_name <> ''
+ BEGIN
+ SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']'
+ EXEC(@cmd)
+ END";
$statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']';
break;
diff --git a/phpBB/includes/db/driver/driver.php b/phpBB/includes/db/driver/driver.php
index 25daa7243d..8dda94bc2c 100644
--- a/phpBB/includes/db/driver/driver.php
+++ b/phpBB/includes/db/driver/driver.php
@@ -206,7 +206,7 @@ class phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
@@ -256,7 +256,7 @@ class phpbb_db_driver
$this->sql_rowseek($rownum, $query_id);
}
- if (!is_object($query_id) && $cache->sql_exists($query_id))
+ if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_fetchfield($query_id, $field);
}
@@ -822,7 +822,7 @@ class phpbb_db_driver
*/
function sql_report($mode, $query = '')
{
- global $cache, $starttime, $phpbb_root_path, $user;
+ global $cache, $starttime, $phpbb_root_path, $phpbb_admin_path, $user;
global $request;
if (is_object($request) && !$request->variable('explain', false))
@@ -852,7 +852,7 @@ class phpbb_db_driver
<head>
<meta charset="utf-8">
<title>SQL Report</title>
- <link href="' . $phpbb_root_path . 'adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" />
+ <link href="' . htmlspecialchars($phpbb_admin_path) . 'style/admin.css" rel="stylesheet" type="text/css" media="screen" />
</head>
<body id="errorpage">
<div id="wrap">
diff --git a/phpBB/includes/db/driver/firebird.php b/phpBB/includes/db/driver/firebird.php
index a55175c345..787c28b812 100644
--- a/phpBB/includes/db/driver/firebird.php
+++ b/phpBB/includes/db/driver/firebird.php
@@ -154,7 +154,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver
}
$this->last_query_text = $query;
- $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;
+ $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
@@ -267,10 +267,10 @@ class phpbb_db_driver_firebird extends phpbb_db_driver
}
}
- if ($cache_ttl)
+ if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
- $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);
+ $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0 && $this->query_result)
{
@@ -330,7 +330,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
@@ -396,7 +396,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
diff --git a/phpBB/includes/db/driver/mssql.php b/phpBB/includes/db/driver/mssql.php
index ac957e7698..89c2c2351b 100644
--- a/phpBB/includes/db/driver/mssql.php
+++ b/phpBB/includes/db/driver/mssql.php
@@ -150,7 +150,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver
$this->sql_report('start', $query);
}
- $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;
+ $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
@@ -165,10 +165,10 @@ class phpbb_db_driver_mssql extends phpbb_db_driver
$this->sql_report('stop', $query);
}
- if ($cache_ttl)
+ if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
- $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);
+ $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0 && $this->query_result)
{
@@ -240,7 +240,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
@@ -277,7 +277,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
@@ -316,7 +316,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
diff --git a/phpBB/includes/db/driver/mssql_odbc.php b/phpBB/includes/db/driver/mssql_odbc.php
index 13e74e66d4..f7834443eb 100644
--- a/phpBB/includes/db/driver/mssql_odbc.php
+++ b/phpBB/includes/db/driver/mssql_odbc.php
@@ -179,7 +179,7 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver
}
$this->last_query_text = $query;
- $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;
+ $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
@@ -194,10 +194,10 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver
$this->sql_report('stop', $query);
}
- if ($cache_ttl)
+ if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
- $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);
+ $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0 && $this->query_result)
{
@@ -270,7 +270,7 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
@@ -311,7 +311,7 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
diff --git a/phpBB/includes/db/driver/mssqlnative.php b/phpBB/includes/db/driver/mssqlnative.php
index 4b1639aba2..656cbd2437 100644
--- a/phpBB/includes/db/driver/mssqlnative.php
+++ b/phpBB/includes/db/driver/mssqlnative.php
@@ -317,7 +317,7 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver
}
$this->last_query_text = $query;
- $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;
+ $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
@@ -337,7 +337,7 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver
if ($cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
- $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);
+ $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0 && $this->query_result)
{
diff --git a/phpBB/includes/db/driver/mysql.php b/phpBB/includes/db/driver/mysql.php
index 6fc6fab483..9de7283a42 100644
--- a/phpBB/includes/db/driver/mysql.php
+++ b/phpBB/includes/db/driver/mysql.php
@@ -188,7 +188,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver
$this->sql_report('start', $query);
}
- $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;
+ $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
@@ -203,10 +203,10 @@ class phpbb_db_driver_mysql extends phpbb_db_driver
$this->sql_report('stop', $query);
}
- if ($cache_ttl)
+ if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
- $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);
+ $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0 && $this->query_result)
{
@@ -265,7 +265,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
@@ -286,7 +286,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
@@ -314,7 +314,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
diff --git a/phpBB/includes/db/driver/mysqli.php b/phpBB/includes/db/driver/mysqli.php
index be28a95715..7448bf1670 100644
--- a/phpBB/includes/db/driver/mysqli.php
+++ b/phpBB/includes/db/driver/mysqli.php
@@ -184,7 +184,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver
$this->sql_report('start', $query);
}
- $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;
+ $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
@@ -199,9 +199,9 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver
$this->sql_report('stop', $query);
}
- if ($cache_ttl)
+ if ($cache && $cache_ttl)
{
- $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);
+ $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
}
else if (defined('DEBUG'))
@@ -256,7 +256,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver
$query_id = $this->query_result;
}
- if (!is_object($query_id) && $cache->sql_exists($query_id))
+ if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
@@ -283,7 +283,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver
$query_id = $this->query_result;
}
- if (!is_object($query_id) && $cache->sql_exists($query_id))
+ if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
@@ -311,7 +311,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver
$query_id = $this->query_result;
}
- if (!is_object($query_id) && $cache->sql_exists($query_id))
+ if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
diff --git a/phpBB/includes/db/driver/oracle.php b/phpBB/includes/db/driver/oracle.php
index 6263ea8414..e21e07055d 100644
--- a/phpBB/includes/db/driver/oracle.php
+++ b/phpBB/includes/db/driver/oracle.php
@@ -267,7 +267,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver
}
$this->last_query_text = $query;
- $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;
+ $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
@@ -443,10 +443,10 @@ class phpbb_db_driver_oracle extends phpbb_db_driver
$this->sql_report('stop', $query);
}
- if ($cache_ttl)
+ if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
- $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);
+ $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0 && $this->query_result)
{
@@ -498,7 +498,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
@@ -550,7 +550,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
@@ -619,7 +619,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
diff --git a/phpBB/includes/db/driver/postgres.php b/phpBB/includes/db/driver/postgres.php
index 147ecd04d9..14854d179d 100644
--- a/phpBB/includes/db/driver/postgres.php
+++ b/phpBB/includes/db/driver/postgres.php
@@ -193,7 +193,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver
}
$this->last_query_text = $query;
- $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;
+ $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
@@ -208,10 +208,10 @@ class phpbb_db_driver_postgres extends phpbb_db_driver
$this->sql_report('stop', $query);
}
- if ($cache_ttl)
+ if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
- $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);
+ $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0 && $this->query_result)
{
@@ -278,7 +278,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
@@ -299,7 +299,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
@@ -348,7 +348,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
diff --git a/phpBB/includes/db/driver/sqlite.php b/phpBB/includes/db/driver/sqlite.php
index 6b9cc64d89..7188f0daa2 100644
--- a/phpBB/includes/db/driver/sqlite.php
+++ b/phpBB/includes/db/driver/sqlite.php
@@ -134,7 +134,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver
$this->sql_report('start', $query);
}
- $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;
+ $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
@@ -149,10 +149,10 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver
$this->sql_report('stop', $query);
}
- if ($cache_ttl)
+ if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
- $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);
+ $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0 && $this->query_result)
{
@@ -210,7 +210,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
@@ -231,7 +231,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
@@ -259,7 +259,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver
$query_id = $this->query_result;
}
- if ($cache->sql_exists($query_id))
+ if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_1.php b/phpBB/includes/db/migration/data/30x/3_0_1.php
new file mode 100644
index 0000000000..c996a0138a
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_1.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_1_rc1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.1')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_10.php b/phpBB/includes/db/migration/data/30x/3_0_10.php
new file mode 100644
index 0000000000..122f93d6b4
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_10.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_10 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.10', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_10_rc3');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.10')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_10_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_10_rc1.php
new file mode 100644
index 0000000000..0ed05812dc
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_10_rc1.php
@@ -0,0 +1,30 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_10_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.10-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_9');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.add', array('email_max_chunk_size', 50)),
+
+ array('config.update', array('version', '3.0.10-rc1')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_10_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_10_rc2.php
new file mode 100644
index 0000000000..b14b3b00aa
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_10_rc2.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_10_rc2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.10-rc2', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_10_rc1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.10-rc2')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_10_rc3.php b/phpBB/includes/db/migration/data/30x/3_0_10_rc3.php
new file mode 100644
index 0000000000..473057d65d
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_10_rc3.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_10_rc3 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.10-rc3', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_10_rc2');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.10-rc3')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_11.php b/phpBB/includes/db/migration/data/30x/3_0_11.php
new file mode 100644
index 0000000000..e063c699cc
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_11.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_11 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.11', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_11_rc2');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.11')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_11_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_11_rc1.php
new file mode 100644
index 0000000000..dddfc0e0e7
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_11_rc1.php
@@ -0,0 +1,95 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_11_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.11-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_10');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array(&$this, 'cleanup_deactivated_styles'))),
+ array('custom', array(array(&$this, 'delete_orphan_private_messages'))),
+
+ array('config.update', array('version', '3.0.11-rc1')),
+ );
+ }
+
+ public function cleanup_deactivated_styles()
+ {
+ // Updates users having current style a deactivated one
+ $sql = 'SELECT style_id
+ FROM ' . STYLES_TABLE . '
+ WHERE style_active = 0';
+ $result = $this->sql_query($sql);
+
+ $deactivated_style_ids = array();
+ while ($style_id = $this->db->sql_fetchfield('style_id', false, $result))
+ {
+ $deactivated_style_ids[] = (int) $style_id;
+ }
+ $this->db->sql_freeresult($result);
+
+ if (!empty($deactivated_style_ids))
+ {
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET user_style = ' . (int) $this->config['default_style'] .'
+ WHERE ' . $this->db->sql_in_set('user_style', $deactivated_style_ids);
+ $this->sql_query($sql);
+ }
+ }
+
+ public function delete_orphan_private_messages()
+ {
+ // Delete orphan private messages
+ $batch_size = 500;
+
+ $sql_array = array(
+ 'SELECT' => 'p.msg_id',
+ 'FROM' => array(
+ PRIVMSGS_TABLE => 'p',
+ ),
+ 'LEFT_JOIN' => array(
+ array(
+ 'FROM' => array(PRIVMSGS_TO_TABLE => 't'),
+ 'ON' => 'p.msg_id = t.msg_id',
+ ),
+ ),
+ 'WHERE' => 't.user_id IS NULL',
+ );
+ $sql = $this->db->sql_build_query('SELECT', $sql_array);
+
+ $result = $this->db->sql_query_limit($sql, $batch_size);
+
+ $delete_pms = array();
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $delete_pms[] = (int) $row['msg_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (!empty($delete_pms))
+ {
+ $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('msg_id', $delete_pms);
+ $this->sql_query($sql);
+
+ // Return false to have the Migrator call this function again
+ return false;
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_11_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_11_rc2.php
new file mode 100644
index 0000000000..fac8523e8c
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_11_rc2.php
@@ -0,0 +1,50 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_11_rc2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.11-rc2', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_11_rc1');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_columns' => array(
+ $this->table_prefix . 'profile_fields' => array(
+ 'field_show_novalue' => array('BOOL', 0),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_columns' => array(
+ $this->table_prefix . 'profile_fields' => array(
+ 'field_show_novalue',
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.11-rc2')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_12_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_12_rc1.php
new file mode 100644
index 0000000000..6a31a51201
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_12_rc1.php
@@ -0,0 +1,123 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/** @todo DROP LOGIN_ATTEMPT_TABLE.attempt_id in 3.0.12-RC1 **/
+
+class phpbb_db_migration_data_30x_3_0_12_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.12-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_11');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array(&$this, 'update_module_auth'))),
+ array('custom', array(array(&$this, 'update_bots'))),
+ array('custom', array(array(&$this, 'disable_bots_from_receiving_pms'))),
+
+ array('config.update', array('version', '3.0.12-rc1')),
+ );
+ }
+
+ public function disable_bots_from_receiving_pms()
+ {
+ // Disable receiving pms for bots
+ $sql = 'SELECT user_id
+ FROM ' . BOTS_TABLE;
+ $result = $this->db->sql_query($sql);
+
+ $bot_user_ids = array();
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $bot_user_ids[] = (int) $row['user_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (!empty($bot_user_ids))
+ {
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET user_allow_pm = 0
+ WHERE ' . $this->db->sql_in_set('user_id', $bot_user_ids);
+ $this->sql_query($sql);
+ }
+ }
+
+ public function update_module_auth()
+ {
+ $sql = 'UPDATE ' . MODULES_TABLE . '
+ SET module_auth = \'acl_u_sig\'
+ WHERE module_class = \'ucp\'
+ AND module_basename = \'profile\'
+ AND module_mode = \'signature\'';
+ $this->sql_query($sql);
+ }
+
+ public function update_bots()
+ {
+ // Update bots
+ if (!function_exists('user_delete'))
+ {
+ include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext);
+ }
+
+ $bots_updates = array(
+ // Bot Deletions
+ 'NG-Search [Bot]' => false,
+ 'Nutch/CVS [Bot]' => false,
+ 'OmniExplorer [Bot]' => false,
+ 'Seekport [Bot]' => false,
+ 'Synoo [Bot]' => false,
+ 'WiseNut [Bot]' => false,
+
+ // Bot Updates
+ // Bot name to bot user agent map
+ 'Baidu [Spider]' => 'Baiduspider',
+ 'Exabot [Bot]' => 'Exabot',
+ 'Voyager [Bot]' => 'voyager/',
+ 'W3C [Validator]' => 'W3C_Validator',
+ );
+
+ foreach ($bots_updates as $bot_name => $bot_agent)
+ {
+ $sql = 'SELECT user_id
+ FROM ' . USERS_TABLE . '
+ WHERE user_type = ' . USER_IGNORE . "
+ AND username_clean = '" . $this->db->sql_escape(utf8_clean_string($bot_name)) . "'";
+ $result = $this->db->sql_query($sql);
+ $bot_user_id = (int) $this->db->sql_fetchfield('user_id');
+ $this->db->sql_freeresult($result);
+
+ if ($bot_user_id)
+ {
+ if ($bot_agent === false)
+ {
+ $sql = 'DELETE FROM ' . BOTS_TABLE . "
+ WHERE user_id = $bot_user_id";
+ $this->sql_query($sql);
+
+ user_delete('remove', $bot_user_id);
+ }
+ else
+ {
+ $sql = 'UPDATE ' . BOTS_TABLE . "
+ SET bot_agent = '" . $this->db->sql_escape($bot_agent) . "'
+ WHERE user_id = $bot_user_id";
+ $this->sql_query($sql);
+ }
+ }
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_1_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_1_rc1.php
new file mode 100644
index 0000000000..562ccf077c
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_1_rc1.php
@@ -0,0 +1,108 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_1_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.1-rc1', '>=');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_columns' => array(
+ $this->table_prefix . 'forums' => array(
+ 'display_subforum_list' => array('BOOL', 1),
+ ),
+ $this->table_prefix . 'sessions' => array(
+ 'session_forum_id' => array('UINT', 0),
+ ),
+ ),
+ 'drop_keys' => array(
+ $this->table_prefix . 'groups' => array(
+ 'group_legend',
+ ),
+ ),
+ 'add_index' => array(
+ $this->table_prefix . 'sessions' => array(
+ 'session_forum_id' => array('session_forum_id'),
+ ),
+ $this->table_prefix . 'groups' => array(
+ 'group_legend_name' => array('group_legend', 'group_name'),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_columns' => array(
+ $this->table_prefix . 'forums' => array(
+ 'display_subforum_list',
+ ),
+ $this->table_prefix . 'sessions' => array(
+ 'session_forum_id',
+ ),
+ ),
+ 'add_index' => array(
+ $this->table_prefix . 'groups' => array(
+ 'group_legend' => array('group_legend'),
+ ),
+ ),
+ 'drop_keys' => array(
+ $this->table_prefix . 'sessions' => array(
+ 'session_forum_id',
+ ),
+ $this->table_prefix . 'groups' => array(
+ 'group_legend_name',
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array(&$this, 'fix_unset_last_view_time'))),
+ array('custom', array(array(&$this, 'reset_smiley_size'))),
+
+ array('config.update', array('version', '3.0.1-rc1')),
+ );
+ }
+
+ public function fix_unset_last_view_time()
+ {
+ $sql = 'UPDATE ' . $this->table_prefix . "topics
+ SET topic_last_view_time = topic_last_post_time
+ WHERE topic_last_view_time = 0";
+ $this->sql_query($sql);
+ }
+
+ public function reset_smiley_size()
+ {
+ // Update smiley sizes
+ $smileys = array('icon_e_surprised.gif', 'icon_eek.gif', 'icon_cool.gif', 'icon_lol.gif', 'icon_mad.gif', 'icon_razz.gif', 'icon_redface.gif', 'icon_cry.gif', 'icon_evil.gif', 'icon_twisted.gif', 'icon_rolleyes.gif', 'icon_exclaim.gif', 'icon_question.gif', 'icon_idea.gif', 'icon_arrow.gif', 'icon_neutral.gif', 'icon_mrgreen.gif', 'icon_e_ugeek.gif');
+
+ foreach ($smileys as $smiley)
+ {
+ if (file_exists($this->phpbb_root_path . 'images/smilies/' . $smiley))
+ {
+ list($width, $height) = getimagesize($this->phpbb_root_path . 'images/smilies/' . $smiley);
+
+ $sql = 'UPDATE ' . SMILIES_TABLE . '
+ SET smiley_width = ' . $width . ', smiley_height = ' . $height . "
+ WHERE smiley_url = '" . $this->db->sql_escape($smiley) . "'";
+
+ $this->sql_query($sql);
+ }
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_2.php b/phpBB/includes/db/migration/data/30x/3_0_2.php
new file mode 100644
index 0000000000..eed5acef82
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_2.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.2', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_2_rc2');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.2')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_2_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_2_rc1.php
new file mode 100644
index 0000000000..a960e90765
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_2_rc1.php
@@ -0,0 +1,32 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_2_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.2-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.add', array('referer_validation', '1')),
+ array('config.add', array('check_attachment_content', '1')),
+ array('config.add', array('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title')),
+
+ array('config.update', array('version', '3.0.2-rc1')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_2_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_2_rc2.php
new file mode 100644
index 0000000000..8917dfea77
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_2_rc2.php
@@ -0,0 +1,80 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_2_rc2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.2-rc2', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_2_rc1');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'change_columns' => array(
+ $this->table_prefix . 'drafts' => array(
+ 'draft_subject' => array('STEXT_UNI', ''),
+ ),
+ $this->table_prefix . 'forums' => array(
+ 'forum_last_post_subject' => array('STEXT_UNI', ''),
+ ),
+ $this->table_prefix . 'posts' => array(
+ 'post_subject' => array('STEXT_UNI', '', 'true_sort'),
+ ),
+ $this->table_prefix . 'privmsgs' => array(
+ 'message_subject' => array('STEXT_UNI', ''),
+ ),
+ $this->table_prefix . 'topics' => array(
+ 'topic_title' => array('STEXT_UNI', '', 'true_sort'),
+ 'topic_last_post_subject' => array('STEXT_UNI', ''),
+ ),
+ ),
+ 'drop_keys' => array(
+ $this->table_prefix . 'sessions' => array(
+ 'session_forum_id',
+ ),
+ ),
+ 'add_index' => array(
+ $this->table_prefix . 'sessions' => array(
+ 'session_fid' => array('session_forum_id'),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'add_index' => array(
+ $this->table_prefix . 'sessions' => array(
+ 'session_forum_id' => array(
+ 'session_forum_id',
+ ),
+ ),
+ ),
+ 'drop_keys' => array(
+ $this->table_prefix . 'sessions' => array(
+ 'session_fid',
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.2-rc2')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_3.php b/phpBB/includes/db/migration/data/30x/3_0_3.php
new file mode 100644
index 0000000000..8984cf7b76
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_3.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_3 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.3', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_3_rc1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.3')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_3_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_3_rc1.php
new file mode 100644
index 0000000000..4b102e1a2e
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_3_rc1.php
@@ -0,0 +1,83 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_3_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.3-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_2');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_columns' => array(
+ $this->table_prefix . 'styles_template' => array(
+ 'template_inherits_id' => array('UINT:4', 0),
+ 'template_inherit_path' => array('VCHAR', ''),
+ ),
+ $this->table_prefix . 'groups' => array(
+ 'group_max_recipients' => array('UINT', 0),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_columns' => array(
+ $this->table_prefix . 'styles_template' => array(
+ 'template_inherits_id',
+ 'template_inherit_path',
+ ),
+ $this->table_prefix . 'groups' => array(
+ 'group_max_recipients',
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.add', array('enable_queue_trigger', '0')),
+ array('config.add', array('queue_trigger_posts', '3')),
+ array('config.add', array('pm_max_recipients', '0')),
+ array('custom', array(array(&$this, 'set_group_default_max_recipients'))),
+ array('config.add', array('dbms_version', $this->db->sql_server_info(true))),
+ array('permission.add', array('u_masspm_group', true, 'u_masspm')),
+ array('custom', array(array(&$this, 'correct_acp_email_permissions'))),
+
+ array('config.update', array('version', '3.0.3-rc1')),
+ );
+ }
+
+ public function correct_acp_email_permissions()
+ {
+ $sql = 'UPDATE ' . $this->table_prefix . 'modules
+ SET module_auth = \'acl_a_email && cfg_email_enable\'
+ WHERE module_class = \'acp\'
+ AND module_basename = \'email\'';
+ $this->sql_query($sql);
+ }
+
+ public function set_group_default_max_recipients()
+ {
+ // Set maximum number of recipients for the registered users, bots, guests group
+ $sql = 'UPDATE ' . GROUPS_TABLE . ' SET group_max_recipients = 5
+ WHERE ' . $this->db->sql_in_set('group_name', array('GUESTS', 'REGISTERED', 'REGISTERED_COPPA', 'BOTS'));
+ $this->sql_query($sql);
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_4.php b/phpBB/includes/db/migration/data/30x/3_0_4.php
new file mode 100644
index 0000000000..9a0c132e78
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_4.php
@@ -0,0 +1,49 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_4 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.4', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_4_rc1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array(&$this, 'rename_log_delete_topic'))),
+
+ array('config.update', array('version', '3.0.4')),
+ );
+ }
+
+ public function rename_log_delete_topic()
+ {
+ if ($this->db->sql_layer == 'oracle')
+ {
+ // log_operation is CLOB - but we can change this later
+ $sql = 'UPDATE ' . $this->table_prefix . "log
+ SET log_operation = 'LOG_DELETE_TOPIC'
+ WHERE log_operation LIKE 'LOG_TOPIC_DELETED'";
+ $this->sql_query($sql);
+ }
+ else
+ {
+ $sql = 'UPDATE ' . $this->table_prefix . "log
+ SET log_operation = 'LOG_DELETE_TOPIC'
+ WHERE log_operation = 'LOG_TOPIC_DELETED'";
+ $this->sql_query($sql);
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_4_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_4_rc1.php
new file mode 100644
index 0000000000..8ad75a557b
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_4_rc1.php
@@ -0,0 +1,123 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_4_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.4-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_3');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_columns' => array(
+ $this->table_prefix . 'profile_fields' => array(
+ 'field_show_profile' => array('BOOL', 0),
+ ),
+ ),
+ 'change_columns' => array(
+ $this->table_prefix . 'styles' => array(
+ 'style_id' => array('UINT', NULL, 'auto_increment'),
+ 'template_id' => array('UINT', 0),
+ 'theme_id' => array('UINT', 0),
+ 'imageset_id' => array('UINT', 0),
+ ),
+ $this->table_prefix . 'styles_imageset' => array(
+ 'imageset_id' => array('UINT', NULL, 'auto_increment'),
+ ),
+ $this->table_prefix . 'styles_imageset_data' => array(
+ 'image_id' => array('UINT', NULL, 'auto_increment'),
+ 'imageset_id' => array('UINT', 0),
+ ),
+ $this->table_prefix . 'styles_theme' => array(
+ 'theme_id' => array('UINT', NULL, 'auto_increment'),
+ ),
+ $this->table_prefix . 'styles_template' => array(
+ 'template_id' => array('UINT', NULL, 'auto_increment'),
+ ),
+ $this->table_prefix . 'styles_template_data' => array(
+ 'template_id' => array('UINT', 0),
+ ),
+ $this->table_prefix . 'forums' => array(
+ 'forum_style' => array('UINT', 0),
+ ),
+ $this->table_prefix . 'users' => array(
+ 'user_style' => array('UINT', 0),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_columns' => array(
+ $this->table_prefix . 'profile_fields' => array(
+ 'field_show_profile',
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array(&$this, 'update_custom_profile_fields'))),
+
+ array('config.update', array('version', '3.0.4-rc1')),
+ );
+ }
+
+ public function update_custom_profile_fields()
+ {
+ // Update the Custom Profile Fields based on previous settings to the new format
+ $sql = 'SELECT field_id, field_required, field_show_on_reg, field_hide
+ FROM ' . PROFILE_FIELDS_TABLE;
+ $result = $this->db->sql_query($sql);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $sql_ary = array(
+ 'field_required' => 0,
+ 'field_show_on_reg' => 0,
+ 'field_hide' => 0,
+ 'field_show_profile'=> 0,
+ );
+
+ if ($row['field_required'])
+ {
+ $sql_ary['field_required'] = $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1;
+ }
+ else if ($row['field_show_on_reg'])
+ {
+ $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1;
+ }
+ else if ($row['field_hide'])
+ {
+ // Only administrators and moderators can see this CPF, if the view is enabled, they can see it, otherwise just admins in the acp_users module
+ $sql_ary['field_hide'] = 1;
+ }
+ else
+ {
+ // equivelant to "none", which is the "Display in user control panel" option
+ $sql_ary['field_show_profile'] = 1;
+ }
+
+ $this->sql_query('UPDATE ' . $this->table_prefix . 'profile_fields SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE field_id = ' . $row['field_id'], $errored, $error_ary);
+ }
+
+ $this->db->sql_freeresult($result);
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_5.php b/phpBB/includes/db/migration/data/30x/3_0_5.php
new file mode 100644
index 0000000000..16d2dee457
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_5.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_5 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.5', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_5_rc1part2');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.5')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_5_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_5_rc1.php
new file mode 100644
index 0000000000..ea17cc1e31
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_5_rc1.php
@@ -0,0 +1,124 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_5_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.5-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_4');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'change_columns' => array(
+ $this->table_prefix . 'forums' => array(
+ 'forum_style' => array('UINT', 0),
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ $search_indexing_state = $this->config['search_indexing_state'];
+
+ return array(
+ array('config.add', array('captcha_gd_wave', 0)),
+ array('config.add', array('captcha_gd_3d_noise', 1)),
+ array('config.add', array('captcha_gd_fonts', 1)),
+ array('config.add', array('confirm_refresh', 1)),
+ array('config.add', array('max_num_search_keywords', 10)),
+ array('config.remove', array('search_indexing_state')),
+ array('config.add', array('search_indexing_state', $search_indexing_state, true)),
+ array('custom', array(array(&$this, 'hash_old_passwords'))),
+ array('custom', array(array(&$this, 'update_ichiro_bot'))),
+ );
+ }
+
+ public function hash_old_passwords()
+ {
+ $sql = 'SELECT user_id, user_password
+ FROM ' . $this->table_prefix . 'users
+ WHERE user_pass_convert = 1';
+ $result = $this->db->sql_query($sql);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ if (strlen($row['user_password']) == 32)
+ {
+ $sql_ary = array(
+ 'user_password' => phpbb_hash($row['user_password']),
+ );
+
+ $this->sql_query('UPDATE ' . $this->table_prefix . 'users SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE user_id = ' . $row['user_id']);
+ }
+ }
+ $this->db->sql_freeresult($result);
+ }
+
+ public function update_ichiro_bot()
+ {
+ // Adjust bot entry
+ $sql = 'UPDATE ' . $this->table_prefix . "bots
+ SET bot_agent = 'ichiro/'
+ WHERE bot_agent = 'ichiro/2'";
+ $this->sql_query($sql);
+ }
+
+ public function remove_duplicate_auth_options()
+ {
+ // Before we are able to add a unique key to auth_option, we need to remove duplicate entries
+ $sql = 'SELECT auth_option
+ FROM ' . $this->table_prefix . 'acl_options
+ GROUP BY auth_option
+ HAVING COUNT(*) >= 2';
+ $result = $this->db->sql_query($sql);
+
+ $auth_options = array();
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $auth_options[] = $row['auth_option'];
+ }
+ $this->db->sql_freeresult($result);
+
+ // Remove specific auth options
+ if (!empty($auth_options))
+ {
+ foreach ($auth_options as $option)
+ {
+ // Select auth_option_ids... the largest id will be preserved
+ $sql = 'SELECT auth_option_id
+ FROM ' . ACL_OPTIONS_TABLE . "
+ WHERE auth_option = '" . $db->sql_escape($option) . "'
+ ORDER BY auth_option_id DESC";
+ // sql_query_limit not possible here, due to bug in postgresql layer
+ $result = $this->db->sql_query($sql);
+
+ // Skip first row, this is our original auth option we want to preserve
+ $row = $this->db->sql_fetchrow($result);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ // Ok, remove this auth option...
+ $this->sql_query('DELETE FROM ' . ACL_OPTIONS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']);
+ $this->sql_query('DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']);
+ $this->sql_query('DELETE FROM ' . ACL_GROUPS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']);
+ $this->sql_query('DELETE FROM ' . ACL_USERS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']);
+ }
+ $this->db->sql_freeresult($result);
+ }
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php b/phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php
new file mode 100644
index 0000000000..8538347b1a
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php
@@ -0,0 +1,42 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_5_rc1part2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.5-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_5_rc1');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'drop_keys' => array(
+ $this->table_prefix . 'acl_options' => array('auth_option'),
+ ),
+ 'add_unique_index' => array(
+ $this->table_prefix . 'acl_options' => array(
+ 'auth_option' => array('auth_option'),
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.5-rc1')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_6.php b/phpBB/includes/db/migration/data/30x/3_0_6.php
new file mode 100644
index 0000000000..bb651dc7cd
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_6.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_6 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.6', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_6_rc4');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.6')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc1.php
new file mode 100644
index 0000000000..38c282ebf0
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_6_rc1.php
@@ -0,0 +1,324 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_6_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.6-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_5');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_columns' => array(
+ $this->table_prefix . 'confirm' => array(
+ 'attempts' => array('UINT', 0),
+ ),
+ $this->table_prefix . 'users' => array(
+ 'user_new' => array('BOOL', 1),
+ 'user_reminded' => array('TINT:4', 0),
+ 'user_reminded_time' => array('TIMESTAMP', 0),
+ ),
+ $this->table_prefix . 'groups' => array(
+ 'group_skip_auth' => array('BOOL', 0, 'after' => 'group_founder_manage'),
+ ),
+ $this->table_prefix . 'privmsgs' => array(
+ 'message_reported' => array('BOOL', 0),
+ ),
+ $this->table_prefix . 'reports' => array(
+ 'pm_id' => array('UINT', 0),
+ ),
+ $this->table_prefix . 'profile_fields' => array(
+ 'field_show_on_vt' => array('BOOL', 0),
+ ),
+ $this->table_prefix . 'forums' => array(
+ 'forum_options' => array('UINT:20', 0),
+ ),
+ ),
+ 'change_columns' => array(
+ $this->table_prefix . 'users' => array(
+ 'user_options' => array('UINT:11', 230271),
+ ),
+ ),
+ 'add_index' => array(
+ $this->table_prefix . 'reports' => array(
+ 'post_id' => array('post_id'),
+ 'pm_id' => array('pm_id'),
+ ),
+ $this->table_prefix . 'posts' => array(
+ 'post_username' => array('post_username:255'),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_columns' => array(
+ $this->table_prefix . 'confirm' => array(
+ 'attempts',
+ ),
+ $this->table_prefix . 'users' => array(
+ 'user_new',
+ 'user_reminded',
+ 'user_reminded_time',
+ ),
+ $this->table_prefix . 'groups' => array(
+ 'group_skip_auth',
+ ),
+ $this->table_prefix . 'privmsgs' => array(
+ 'message_reported',
+ ),
+ $this->table_prefix . 'reports' => array(
+ 'pm_id',
+ ),
+ $this->table_prefix . 'profile_fields' => array(
+ 'field_show_on_vt',
+ ),
+ $this->table_prefix . 'forums' => array(
+ 'forum_options',
+ ),
+ ),
+ 'drop_keys' => array(
+ $this->table_prefix . 'reports' => array(
+ 'post_id',
+ 'pm_id',
+ ),
+ $this->table_prefix . 'posts' => array(
+ 'post_username',
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.add', array('captcha_plugin', 'phpbb_captcha_nogd')),
+ array('if', array(
+ ($this->config['captcha_gd']),
+ array('config.update', array('captcha_plugin', 'phpbb_captcha_gd')),
+ )),
+
+ array('config.add', array('feed_enable', 0)),
+ array('config.add', array('feed_limit', 10)),
+ array('config.add', array('feed_overall_forums', 1)),
+ array('config.add', array('feed_overall_forums_limit', 15)),
+ array('config.add', array('feed_overall_topics', 0)),
+ array('config.add', array('feed_overall_topics_limit', 15)),
+ array('config.add', array('feed_forum', 1)),
+ array('config.add', array('feed_topic', 1)),
+ array('config.add', array('feed_item_statistics', 1)),
+
+ array('config.add', array('smilies_per_page', 50)),
+ array('config.add', array('allow_pm_report', 1)),
+ array('config.add', array('min_post_chars', 1)),
+ array('config.add', array('allow_quick_reply', 1)),
+ array('config.add', array('new_member_post_limit', 0)),
+ array('config.add', array('new_member_group_default', 0)),
+ array('config.add', array('delete_time', $this->config['edit_time'])),
+
+ array('config.add', array('allow_avatar', 0)),
+ array('if', array(
+ ($this->config['allow_avatar_upload'] || $this->config['allow_avatar_local'] || $this->config['allow_avatar_remote']),
+ array('config.update', array('allow_avatar', 1)),
+ )),
+ array('config.add', array('allow_avatar_remote_upload', 0)),
+ array('if', array(
+ ($this->config['allow_avatar_remote'] && $this->config['allow_avatar_upload']),
+ array('config.update', array('allow_avatar_remote_upload', 1)),
+ )),
+
+ array('module.add', array(
+ 'acp',
+ 'ACP_BOARD_CONFIGURATION',
+ array(
+ 'module_basename' => 'acp_board',
+ 'modes' => array('feed'),
+ ),
+ )),
+ array('module.add', array(
+ 'acp',
+ 'ACP_CAT_USERS',
+ array(
+ 'module_basename' => 'acp_users',
+ 'modes' => array('warnings'),
+ ),
+ )),
+ array('module.add', array(
+ 'acp',
+ 'ACP_SERVER_CONFIGURATION',
+ array(
+ 'module_basename' => 'acp_send_statistics',
+ 'modes' => array('send_statistics'),
+ ),
+ )),
+ array('module.add', array(
+ 'acp',
+ 'ACP_FORUM_BASED_PERMISSIONS',
+ array(
+ 'module_basename' => 'acp_permissions',
+ 'modes' => array('setting_forum_copy'),
+ ),
+ )),
+ array('module.add', array(
+ 'mcp',
+ 'MCP_REPORTS',
+ array(
+ 'module_basename' => 'mcp_pm_reports',
+ 'modes' => array('pm_reports','pm_reports_closed','pm_report_details'),
+ ),
+ )),
+ array('custom', array(array(&$this, 'add_newly_registered_group'))),
+ array('custom', array(array(&$this, 'set_user_options_default'))),
+
+ array('config.update', array('version', '3.0.6-rc1')),
+ );
+ }
+
+ public function set_user_options_default()
+ {
+ // 229376 is the added value to enable all three signature options
+ $sql = 'UPDATE ' . USERS_TABLE . ' SET user_options = user_options + 229376';
+ $this->sql_query($sql);
+ }
+
+ public function add_newly_registered_group()
+ {
+ // Add newly_registered group... but check if it already exists (we always supported running the updater on any schema)
+ $sql = 'SELECT group_id
+ FROM ' . GROUPS_TABLE . "
+ WHERE group_name = 'NEWLY_REGISTERED'";
+ $result = $this->db->sql_query($sql);
+ $group_id = (int) $this->db->sql_fetchfield('group_id');
+ $this->db->sql_freeresult($result);
+
+ if (!$group_id)
+ {
+ $sql = 'INSERT INTO ' . GROUPS_TABLE . " (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('NEWLY_REGISTERED', 3, 0, '', 0, '', '', '', 5)";
+ $this->sql_query($sql);
+
+ $group_id = $this->db->sql_nextid();
+ }
+
+ // Insert new user role... at the end of the chain
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = 'ROLE_USER_NEW_MEMBER'
+ AND role_type = 'u_'";
+ $result = $this->db->sql_query($sql);
+ $u_role = (int) $this->db->sql_fetchfield('role_id');
+ $this->db->sql_freeresult($result);
+
+ if (!$u_role)
+ {
+ $sql = 'SELECT MAX(role_order) as max_order_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_type = 'u_'";
+ $result = $this->db->sql_query($sql);
+ $next_order_id = (int) $this->db->sql_fetchfield('max_order_id');
+ $this->db->sql_freeresult($result);
+
+ $next_order_id++;
+
+ $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NEW_MEMBER', 'ROLE_DESCRIPTION_USER_NEW_MEMBER', 'u_', $next_order_id)";
+ $this->sql_query($sql);
+ $u_role = $this->db->sql_nextid();
+
+ // Now add the correct data to the roles...
+ // The standard role says that new users are not able to send a PM, Mass PM, are not able to PM groups
+ $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $u_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group')";
+ $this->sql_query($sql);
+
+ // Add user role to group
+ $sql = 'INSERT INTO ' . ACL_GROUPS_TABLE . " (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES ($group_id, 0, 0, $u_role, 0)";
+ $this->sql_query($sql);
+ }
+
+ // Insert new forum role
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = 'ROLE_FORUM_NEW_MEMBER'
+ AND role_type = 'f_'";
+ $result = $this->db->sql_query($sql);
+ $f_role = (int) $this->db->sql_fetchfield('role_id');
+ $this->db->sql_freeresult($result);
+
+ if (!$f_role)
+ {
+ $sql = 'SELECT MAX(role_order) as max_order_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_type = 'f_'";
+ $result = $this->db->sql_query($sql);
+ $next_order_id = (int) $this->db->sql_fetchfield('max_order_id');
+ $this->db->sql_freeresult($result);
+
+ $next_order_id++;
+
+ $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_NEW_MEMBER', 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER', 'f_', $next_order_id)";
+ $this->sql_query($sql);
+ $f_role = $this->db->sql_nextid();
+
+ $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $f_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove')";
+ $this->sql_query($sql);
+ }
+
+ // Set every members user_new column to 0 (old users) only if there is no one yet (this makes sure we do not execute this more than once)
+ $sql = 'SELECT 1
+ FROM ' . USERS_TABLE . '
+ WHERE user_new = 0';
+ $result = $this->db->sql_query_limit($sql, 1);
+ $row = $this->db->sql_fetchrow($result);
+ $this->db->sql_freeresult($result);
+
+ if (!$row)
+ {
+ $sql = 'UPDATE ' . USERS_TABLE . ' SET user_new = 0';
+ $this->sql_query($sql);
+ }
+
+ // To mimick the old "feature" we will assign the forum role to every forum, regardless of the setting (this makes sure there are no "this does not work!!!! YUO!!!" posts...
+ // Check if the role is already assigned...
+ $sql = 'SELECT forum_id
+ FROM ' . ACL_GROUPS_TABLE . '
+ WHERE group_id = ' . $group_id . '
+ AND auth_role_id = ' . $f_role;
+ $result = $this->db->sql_query($sql);
+ $is_options = (int) $this->db->sql_fetchfield('forum_id');
+ $this->db->sql_freeresult($result);
+
+ // Not assigned at all... :/
+ if (!$is_options)
+ {
+ // Get postable forums
+ $sql = 'SELECT forum_id
+ FROM ' . FORUMS_TABLE . '
+ WHERE forum_type != ' . FORUM_LINK;
+ $result = $this->db->sql_query($sql);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $this->sql_query('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (' . $group_id . ', ' . (int) $row['forum_id'] . ', 0, ' . $f_role . ', 0)');
+ }
+ $this->db->sql_freeresult($result);
+ }
+
+ // Clear permissions...
+ include_once($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext);
+ $auth_admin = new auth_admin();
+ $auth_admin->acl_clear_prefetch();
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc2.php
new file mode 100644
index 0000000000..a939dbd489
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_6_rc2.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_6_rc2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.6-rc2', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_6_rc1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.6-rc2')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc3.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc3.php
new file mode 100644
index 0000000000..b3f09d8ab8
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_6_rc3.php
@@ -0,0 +1,40 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_6_rc3 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.6-rc3', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_6_rc2');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array(&$this, 'update_cp_fields'))),
+
+ array('config.update', array('version', '3.0.6-rc3')),
+ );
+ }
+
+ public function update_cp_fields()
+ {
+ // Update the Custom Profile Fields based on previous settings to the new format
+ $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . '
+ SET field_show_on_vt = 1
+ WHERE field_hide = 0
+ AND (field_required = 1 OR field_show_on_reg = 1 OR field_show_profile = 1)';
+ $this->sql_query($sql);
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc4.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc4.php
new file mode 100644
index 0000000000..fc2923f99b
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_6_rc4.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_6_rc4 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.6-rc4', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_6_rc3');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.6-rc4')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_7.php b/phpBB/includes/db/migration/data/30x/3_0_7.php
new file mode 100644
index 0000000000..9ff2e9e4ab
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_7.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_7 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.7', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_7_rc2');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.7')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_7_pl1.php b/phpBB/includes/db/migration/data/30x/3_0_7_pl1.php
new file mode 100644
index 0000000000..c9cc9d19ac
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_7_pl1.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_7_pl1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.7-pl1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_7');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.7-pl1')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_7_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_7_rc1.php
new file mode 100644
index 0000000000..ffebf66f2d
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_7_rc1.php
@@ -0,0 +1,76 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_7_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.7-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_6');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'drop_keys' => array(
+ $this->table_prefix . 'log' => array(
+ 'log_time',
+ ),
+ ),
+ 'add_index' => array(
+ $this->table_prefix . 'topics_track' => array(
+ 'topic_id' => array('topic_id'),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'add_index' => array(
+ $this->table_prefix . 'log' => array(
+ 'log_time' => array('log_time'),
+ ),
+ ),
+ 'drop_keys' => array(
+ $this->table_prefix . 'topics_track' => array(
+ 'topic_id',
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.add', array('feed_overall', 1)),
+ array('config.add', array('feed_http_auth', 0)),
+ array('config.add', array('feed_limit_post', $this->config['feed_limit'])),
+ array('config.add', array('feed_limit_topic', $this->config['feed_overall_topics_limit'])),
+ array('config.add', array('feed_topics_new', $this->config['feed_overall_topics'])),
+ array('config.add', array('feed_topics_active', $this->config['feed_overall_topics'])),
+ array('custom', array(array(&$this, 'delete_text_templates'))),
+
+ array('config.update', array('version', '3.0.7-rc1')),
+ );
+ }
+
+ public function delete_text_templates()
+ {
+ // Delete all text-templates from the template_data
+ $sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . '
+ WHERE template_filename ' . $this->db->sql_like_expression($this->db->any_char . '.txt');
+ $this->sql_query($sql);
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_7_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_7_rc2.php
new file mode 100644
index 0000000000..55bc2bc679
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_7_rc2.php
@@ -0,0 +1,73 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_7_rc2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.7-rc2', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_7_rc1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array(&$this, 'update_email_hash'))),
+
+ array('config.update', array('version', '3.0.7-rc2')),
+ );
+ }
+
+ public function update_email_hash($start = 0)
+ {
+ $limit = 1000;
+
+ $sql = 'SELECT user_id, user_email, user_email_hash
+ FROM ' . USERS_TABLE . '
+ WHERE user_type <> ' . USER_IGNORE . "
+ AND user_email <> ''";
+ $result = $this->db->sql_query_limit($sql, $limit, $start);
+
+ $i = 0;
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $i++;
+
+ // Snapshot of the phpbb_email_hash() function
+ // We cannot call it directly because the auto updater updates the DB first. :/
+ $user_email_hash = sprintf('%u', crc32(strtolower($row['user_email']))) . strlen($row['user_email']);
+
+ if ($user_email_hash != $row['user_email_hash'])
+ {
+ $sql_ary = array(
+ 'user_email_hash' => $user_email_hash,
+ );
+
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . '
+ WHERE user_id = ' . (int) $row['user_id'];
+ $this->sql_query($sql);
+ }
+ }
+ $this->db->sql_freeresult($result);
+
+ if ($i < $limit)
+ {
+ // Completed
+ return;
+ }
+
+ // Return the next start, will be sent to $start when this function is called again
+ return $start + $limit;
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_8.php b/phpBB/includes/db/migration/data/30x/3_0_8.php
new file mode 100644
index 0000000000..8998ef9627
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_8.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_8 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.8', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_8_rc1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.8')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_8_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_8_rc1.php
new file mode 100644
index 0000000000..aeff35333e
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_8_rc1.php
@@ -0,0 +1,221 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_8_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.8-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_7_pl1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array(&$this, 'update_file_extension_group_names'))),
+ array('custom', array(array(&$this, 'update_module_auth'))),
+ array('custom', array(array(&$this, 'update_bots'))),
+ array('custom', array(array(&$this, 'delete_orphan_shadow_topics'))),
+ array('module.add', array(
+ 'acp',
+ 'ACP_MESSAGES',
+ array(
+ 'module_basename' => 'acp_board',
+ 'modes' => array('post'),
+ ),
+ )),
+ array('config.add', array('load_unreads_search', 1)),
+ array('config.update_if_equals', array(600, 'queue_interval', 60)),
+ array('config.update_if_equals', array(50, 'email_package_size', 20)),
+
+ array('config.update', array('version', '3.0.8-rc1')),
+ );
+ }
+
+ public function update_file_extension_group_names()
+ {
+ // Update file extension group names to use language strings.
+ $sql = 'SELECT lang_dir
+ FROM ' . LANG_TABLE;
+ $result = $this->db->sql_query($sql);
+
+ $extension_groups_updated = array();
+ while ($lang_dir = $this->db->sql_fetchfield('lang_dir'))
+ {
+ $lang_dir = basename($lang_dir);
+
+ // The language strings we need are either in language/.../acp/attachments.php
+ // in the update package if we're updating to 3.0.8-RC1 or later,
+ // or they are in language/.../install.php when we're updating from 3.0.7-PL1 or earlier.
+ // On an already updated board, they can also already be in language/.../acp/attachments.php
+ // in the board root.
+ $lang_files = array(
+ "{$this->phpbb_root_path}install/update/new/language/$lang_dir/acp/attachments.{$this->php_ext}",
+ "{$this->phpbb_root_path}language/$lang_dir/install.{$this->php_ext}",
+ "{$this->phpbb_root_path}language/$lang_dir/acp/attachments.{$this->php_ext}",
+ );
+
+ foreach ($lang_files as $lang_file)
+ {
+ if (!file_exists($lang_file))
+ {
+ continue;
+ }
+
+ $lang = array();
+ include($lang_file);
+
+ foreach($lang as $lang_key => $lang_val)
+ {
+ if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0)
+ {
+ continue;
+ }
+
+ $sql_ary = array(
+ 'group_name' => substr($lang_key, 10), // Strip off 'EXT_GROUP_'
+ );
+
+ $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . '
+ SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . "
+ WHERE group_name = '" . $this->db->sql_escape($lang_val) . "'";
+ $this->sql_query($sql);
+
+ $extension_groups_updated[$lang_key] = true;
+ }
+ }
+ }
+ $this->db->sql_freeresult($result);
+ }
+
+ public function update_module_auth()
+ {
+ $sql = 'UPDATE ' . MODULES_TABLE . '
+ SET module_auth = \'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)\'
+ WHERE module_class = \'ucp\'
+ AND module_basename = \'profile\'
+ AND module_mode = \'avatar\'';
+ $this->sql_query($sql);
+ }
+
+ public function update_bots()
+ {
+ $bot_name = 'Bing [Bot]';
+ $bot_name_clean = utf8_clean_string($bot_name);
+
+ $sql = 'SELECT user_id
+ FROM ' . USERS_TABLE . "
+ WHERE username_clean = '" . $this->db->sql_escape($bot_name_clean) . "'";
+ $result = $this->db->sql_query($sql);
+ $bing_already_added = (bool) $this->db->sql_fetchfield('user_id');
+ $this->db->sql_freeresult($result);
+
+ if (!$bing_already_added)
+ {
+ $bot_agent = 'bingbot/';
+ $bot_ip = '';
+ $sql = 'SELECT group_id, group_colour
+ FROM ' . GROUPS_TABLE . "
+ WHERE group_name = 'BOTS'";
+ $result = $this->db->sql_query($sql);
+ $group_row = $this->db->sql_fetchrow($result);
+ $this->db->sql_freeresult($result);
+
+ if (!$group_row)
+ {
+ // default fallback, should never get here
+ $group_row['group_id'] = 6;
+ $group_row['group_colour'] = '9E8DA7';
+ }
+
+ if (!function_exists('user_add'))
+ {
+ include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext);
+ }
+
+ $user_row = array(
+ 'user_type' => USER_IGNORE,
+ 'group_id' => $group_row['group_id'],
+ 'username' => $bot_name,
+ 'user_regdate' => time(),
+ 'user_password' => '',
+ 'user_colour' => $group_row['group_colour'],
+ 'user_email' => '',
+ 'user_lang' => $this->config['default_lang'],
+ 'user_style' => $this->config['default_style'],
+ 'user_timezone' => 0,
+ 'user_dateformat' => $this->config['default_dateformat'],
+ 'user_allow_massemail' => 0,
+ );
+
+ $user_id = user_add($user_row);
+
+ $sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', array(
+ 'bot_active' => 1,
+ 'bot_name' => (string) $bot_name,
+ 'user_id' => (int) $user_id,
+ 'bot_agent' => (string) $bot_agent,
+ 'bot_ip' => (string) $bot_ip,
+ ));
+
+ $this->sql_query($sql);
+ }
+ }
+
+ public function delete_orphan_shadow_topics()
+ {
+ // Delete shadow topics pointing to not existing topics
+ $batch_size = 500;
+
+ // Set of affected forums we have to resync
+ $sync_forum_ids = array();
+
+ $sql_array = array(
+ 'SELECT' => 't1.topic_id, t1.forum_id',
+ 'FROM' => array(
+ TOPICS_TABLE => 't1',
+ ),
+ 'LEFT_JOIN' => array(
+ array(
+ 'FROM' => array(TOPICS_TABLE => 't2'),
+ 'ON' => 't1.topic_moved_id = t2.topic_id',
+ ),
+ ),
+ 'WHERE' => 't1.topic_moved_id <> 0
+ AND t2.topic_id IS NULL',
+ );
+ $sql = $this->db->sql_build_query('SELECT', $sql_array);
+ $result = $this->db->sql_query_limit($sql, $batch_size);
+
+ $topic_ids = array();
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $topic_ids[] = (int) $row['topic_id'];
+
+ $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (!empty($topic_ids))
+ {
+ $sql = 'DELETE FROM ' . TOPICS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('topic_id', $topic_ids);
+ $this->db->sql_query($sql);
+
+ // Sync the forums we have deleted shadow topics from.
+ sync('forum', 'forum_id', $sync_forum_ids, true, true);
+
+ return false;
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_9.php b/phpBB/includes/db/migration/data/30x/3_0_9.php
new file mode 100644
index 0000000000..d5269ea6f0
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_9.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_9 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.9', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_9_rc4');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.9')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc1.php
new file mode 100644
index 0000000000..1f8622798e
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_9_rc1.php
@@ -0,0 +1,124 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_9_rc1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.9-rc1', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_8');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_tables' => array(
+ $this->table_prefix . 'login_attempts' => array(
+ 'COLUMNS' => array(
+ // this column was removed from the database updater
+ // after 3.0.9-RC3 was released. It might still exist
+ // in 3.0.9-RCX installations and has to be dropped in
+ // 3.0.12 after the db_tools class is capable of properly
+ // removing a primary key.
+ // 'attempt_id' => array('UINT', NULL, 'auto_increment'),
+ 'attempt_ip' => array('VCHAR:40', ''),
+ 'attempt_browser' => array('VCHAR:150', ''),
+ 'attempt_forwarded_for' => array('VCHAR:255', ''),
+ 'attempt_time' => array('TIMESTAMP', 0),
+ 'user_id' => array('UINT', 0),
+ 'username' => array('VCHAR_UNI:255', 0),
+ 'username_clean' => array('VCHAR_CI', 0),
+ ),
+ //'PRIMARY_KEY' => 'attempt_id',
+ 'KEYS' => array(
+ 'att_ip' => array('INDEX', array('attempt_ip', 'attempt_time')),
+ 'att_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')),
+ 'att_time' => array('INDEX', array('attempt_time')),
+ 'user_id' => array('INDEX', 'user_id'),
+ ),
+ ),
+ ),
+ 'change_columns' => array(
+ $this->table_prefix . 'bbcodes' => array(
+ 'bbcode_id' => array('USINT', 0),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_tables' => array(
+ $this->table_prefix . 'login_attempts',
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.add', array('ip_login_limit_max', 50)),
+ array('config.add', array('ip_login_limit_time', 21600)),
+ array('config.add', array('ip_login_limit_use_forwarded', 0)),
+ array('custom', array(array(&$this, 'update_file_extension_group_names'))),
+ array('custom', array(array(&$this, 'fix_firebird_qa_captcha'))),
+
+ array('config.update', array('version', '3.0.9-rc1')),
+ );
+ }
+
+ public function update_file_extension_group_names()
+ {
+ // Update file extension group names to use language strings, again.
+ $sql = 'SELECT group_id, group_name
+ FROM ' . EXTENSION_GROUPS_TABLE . '
+ WHERE group_name ' . $this->db->sql_like_expression('EXT_GROUP_' . $this->db->any_char);
+ $result = $this->db->sql_query($sql);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $sql_ary = array(
+ 'group_name' => substr($row['group_name'], 10), // Strip off 'EXT_GROUP_'
+ );
+
+ $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . '
+ SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . '
+ WHERE group_id = ' . $row['group_id'];
+ $this->sql_query($sql);
+ }
+ $this->db->sql_freeresult($result);
+ }
+
+ public function fix_firebird_qa_captcha()
+ {
+ // Recover from potentially broken Q&A CAPTCHA table on firebird
+ // Q&A CAPTCHA was uninstallable, so it's safe to remove these
+ // without data loss
+ if ($this->db_tools->sql_layer == 'firebird')
+ {
+ $tables = array(
+ $this->table_prefix . 'captcha_questions',
+ $this->table_prefix . 'captcha_answers',
+ $this->table_prefix . 'qa_confirm',
+ );
+ foreach ($tables as $table)
+ {
+ if ($this->db_tools->sql_table_exists($table))
+ {
+ $this->db_tools->sql_table_drop($table);
+ }
+ }
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc2.php
new file mode 100644
index 0000000000..c0e662aa45
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_9_rc2.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_9_rc2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.9-rc2', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_9_rc1');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.9-rc2')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc3.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc3.php
new file mode 100644
index 0000000000..d6d1f14b2e
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_9_rc3.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_9_rc3 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.9-rc3', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_9_rc2');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.9-rc3')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc4.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc4.php
new file mode 100644
index 0000000000..e673249343
--- /dev/null
+++ b/phpBB/includes/db/migration/data/30x/3_0_9_rc4.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_30x_3_0_9_rc4 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.0.9-rc4', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_9_rc3');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('version', '3.0.9-rc4')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/310/dev.php b/phpBB/includes/db/migration/data/310/dev.php
new file mode 100644
index 0000000000..13b36bbf30
--- /dev/null
+++ b/phpBB/includes/db/migration/data/310/dev.php
@@ -0,0 +1,405 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_310_dev extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return version_compare($this->config['version'], '3.1.0-dev', '>=');
+ }
+
+ static public function depends_on()
+ {
+ return array(
+ 'phpbb_db_migration_data_310_extensions',
+ 'phpbb_db_migration_data_310_style_update_p2',
+ 'phpbb_db_migration_data_310_timezone_p2',
+ 'phpbb_db_migration_data_310_reported_posts_display',
+ );
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_columns' => array(
+ $this->table_prefix . 'groups' => array(
+ 'group_teampage' => array('UINT', 0, 'after' => 'group_legend'),
+ ),
+ $this->table_prefix . 'profile_fields' => array(
+ 'field_show_on_pm' => array('BOOL', 0),
+ ),
+ $this->table_prefix . 'styles' => array(
+ 'style_path' => array('VCHAR:100', ''),
+ 'bbcode_bitfield' => array('VCHAR:255', 'kNg='),
+ 'style_parent_id' => array('UINT:4', 0),
+ 'style_parent_tree' => array('TEXT', ''),
+ ),
+ $this->table_prefix . 'reports' => array(
+ 'reported_post_text' => array('MTEXT_UNI', ''),
+ 'reported_post_uid' => array('VCHAR:8', ''),
+ 'reported_post_bitfield' => array('VCHAR:255', ''),
+ ),
+ ),
+ 'change_columns' => array(
+ $this->table_prefix . 'groups' => array(
+ 'group_legend' => array('UINT', 0),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_columns' => array(
+ $this->table_prefix . 'groups' => array(
+ 'group_teampage',
+ ),
+ $this->table_prefix . 'profile_fields' => array(
+ 'field_show_on_pm',
+ ),
+ $this->table_prefix . 'styles' => array(
+ 'style_path',
+ 'bbcode_bitfield',
+ 'style_parent_id',
+ 'style_parent_tree',
+ ),
+ $this->table_prefix . 'reports' => array(
+ 'reported_post_text',
+ 'reported_post_uid',
+ 'reported_post_bitfield',
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('config.update', array('search_type', 'phpbb_search_' . $this->config['search_type'])),
+
+ array('config.add', array('fulltext_postgres_ts_name', 'simple')),
+ array('config.add', array('fulltext_postgres_min_word_len', 4)),
+ array('config.add', array('fulltext_postgres_max_word_len', 254)),
+ array('config.add', array('fulltext_sphinx_stopwords', 0)),
+ array('config.add', array('fulltext_sphinx_indexer_mem_limit', 512)),
+
+ array('config.add', array('load_jquery_cdn', 0)),
+ array('config.add', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js')),
+
+ array('config.add', array('use_system_cron', 0)),
+
+ array('config.add', array('legend_sort_groupname', 0)),
+ array('config.add', array('teampage_forums', 1)),
+ array('config.add', array('teampage_memberships', 1)),
+
+ array('config.add', array('load_cpf_pm', 0)),
+
+ array('config.add', array('display_last_subject', 1)),
+
+ array('config.add', array('assets_version', 1)),
+
+ array('config.add', array('site_home_url', '')),
+ array('config.add', array('site_home_text', '')),
+
+ array('permission.add', array('u_chgprofileinfo', true, 'u_sig')),
+
+ array('module.add', array(
+ 'acp',
+ 'ACP_GROUPS',
+ array(
+ 'module_basename' => 'acp_groups',
+ 'modes' => array('position'),
+ ),
+ )),
+ array('module.add', array(
+ 'acp',
+ 'ACP_ATTACHMENTS',
+ array(
+ 'module_basename' => 'acp_attachments',
+ 'modes' => array('manage'),
+ ),
+ )),
+ array('module.add', array(
+ 'acp',
+ 'ACP_STYLE_MANAGEMENT',
+ array(
+ 'module_basename' => 'acp_styles',
+ 'modes' => array('install', 'cache'),
+ ),
+ )),
+ array('module.add', array(
+ 'ucp',
+ 'UCP_PROFILE',
+ array(
+ 'module_basename' => 'ucp_profile',
+ 'modes' => array('autologin_keys'),
+ ),
+ )),
+ // Module will be renamed later
+ array('module.add', array(
+ 'acp',
+ 'ACP_CAT_STYLES',
+ 'ACP_LANGUAGE'
+ )),
+
+ array('module.remove', array(
+ 'acp',
+ false,
+ 'ACP_TEMPLATES',
+ )),
+ array('module.remove', array(
+ 'acp',
+ false,
+ 'ACP_THEMES',
+ )),
+ array('module.remove', array(
+ 'acp',
+ false,
+ 'ACP_IMAGESETS',
+ )),
+
+ array('custom', array(array($this, 'rename_module_basenames'))),
+ array('custom', array(array($this, 'rename_styles_module'))),
+ array('custom', array(array($this, 'add_group_teampage'))),
+ array('custom', array(array($this, 'update_group_legend'))),
+ array('custom', array(array($this, 'localise_global_announcements'))),
+ array('custom', array(array($this, 'update_ucp_pm_basename'))),
+ array('custom', array(array($this, 'update_ucp_profile_auth'))),
+ array('custom', array(array($this, 'move_customise_modules'))),
+
+ array('config.update', array('version', '3.1.0-dev')),
+ );
+ }
+
+ public function move_customise_modules()
+ {
+ // Move language management to new location in the Customise tab
+ // First get language module id
+ $sql = 'SELECT module_id FROM ' . MODULES_TABLE . "
+ WHERE module_basename = 'acp_language'";
+ $result = $this->db->sql_query($sql);
+ $language_module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+ // Next get language management module id of the one just created
+ $sql = 'SELECT module_id FROM ' . MODULES_TABLE . "
+ WHERE module_langname = 'ACP_LANGUAGE'";
+ $result = $this->db->sql_query($sql);
+ $language_management_module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ if (!class_exists('acp_modules'))
+ {
+ include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext);
+ }
+ // acp_modules calls adm_back_link, which is undefined at this point
+ if (!function_exists('adm_back_link'))
+ {
+ include($this->phpbb_root_path . 'includes/functions_acp.' . $this->php_ext);
+ }
+ $module_manager = new acp_modules();
+ $module_manager->module_class = 'acp';
+ $module_manager->move_module($language_module_id, $language_management_module_id);
+ }
+
+ public function update_ucp_pm_basename()
+ {
+ $sql = 'SELECT module_id, module_basename
+ FROM ' . MODULES_TABLE . "
+ WHERE module_basename <> 'ucp_pm' AND
+ module_langname='UCP_PM'";
+ $result = $this->db->sql_query_limit($sql, 1);
+
+ if ($row = $this->db->sql_fetchrow($result))
+ {
+ // This update is still not applied. Applying it
+
+ $sql = 'UPDATE ' . MODULES_TABLE . "
+ SET module_basename = 'ucp_pm'
+ WHERE module_id = " . (int) $row['module_id'];
+
+ $this->sql_query($sql);
+ }
+ $this->db->sql_freeresult($result);
+ }
+
+ public function update_ucp_profile_auth()
+ {
+ // Update the auth setting for the module
+ $sql = 'UPDATE ' . MODULES_TABLE . "
+ SET module_auth = 'acl_u_chgprofileinfo'
+ WHERE module_class = 'ucp'
+ AND module_basename = 'ucp_profile'
+ AND module_mode = 'profile_info'";
+ $this->sql_query($sql);
+ }
+
+ public function rename_styles_module()
+ {
+ // Rename styles module to Customise
+ $sql = 'UPDATE ' . MODULES_TABLE . "
+ SET module_langname = 'ACP_CAT_CUSTOMISE'
+ WHERE module_langname = 'ACP_CAT_STYLES'";
+ $this->sql_query($sql);
+ }
+
+ public function rename_module_basenames()
+ {
+ // rename all module basenames to full classname
+ $sql = 'SELECT module_id, module_basename, module_class
+ FROM ' . MODULES_TABLE;
+ $result = $this->db->sql_query($sql);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $module_id = (int) $row['module_id'];
+ unset($row['module_id']);
+
+ if (!empty($row['module_basename']) && !empty($row['module_class']))
+ {
+ // all the class names start with class name or with phpbb_ for auto loading
+ if (strpos($row['module_basename'], $row['module_class'] . '_') !== 0 &&
+ strpos($row['module_basename'], 'phpbb_') !== 0)
+ {
+ $row['module_basename'] = $row['module_class'] . '_' . $row['module_basename'];
+
+ $sql_update = $this->db->sql_build_array('UPDATE', $row);
+
+ $sql = 'UPDATE ' . MODULES_TABLE . '
+ SET ' . $sql_update . '
+ WHERE module_id = ' . $module_id;
+ $this->sql_query($sql);
+ }
+ }
+ }
+
+ $this->db->sql_freeresult($result);
+ }
+
+ public function add_group_teampage()
+ {
+ $sql = 'UPDATE ' . GROUPS_TABLE . '
+ SET group_teampage = 1
+ WHERE group_type = ' . GROUP_SPECIAL . "
+ AND group_name = 'ADMINISTRATORS'";
+ $this->sql_query($sql);
+
+ $sql = 'UPDATE ' . GROUPS_TABLE . '
+ SET group_teampage = 2
+ WHERE group_type = ' . GROUP_SPECIAL . "
+ AND group_name = 'GLOBAL_MODERATORS'";
+ $this->sql_query($sql);
+ }
+
+ public function update_group_legend()
+ {
+ $sql = 'SELECT group_id
+ FROM ' . GROUPS_TABLE . '
+ WHERE group_legend = 1
+ ORDER BY group_name ASC';
+ $result = $this->db->sql_query($sql);
+
+ $next_legend = 1;
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $sql = 'UPDATE ' . GROUPS_TABLE . '
+ SET group_legend = ' . $next_legend . '
+ WHERE group_id = ' . (int) $row['group_id'];
+ $this->sql_query($sql);
+
+ $next_legend++;
+ }
+ $this->db->sql_freeresult($result);
+ }
+
+ public function localise_global_announcements()
+ {
+ // Localise Global Announcements
+ $sql = 'SELECT topic_id, topic_approved, (topic_replies + 1) AS topic_posts, topic_last_post_id, topic_last_post_subject, topic_last_post_time, topic_last_poster_id, topic_last_poster_name, topic_last_poster_colour
+ FROM ' . TOPICS_TABLE . '
+ WHERE forum_id = 0
+ AND topic_type = ' . POST_GLOBAL;
+ $result = $this->db->sql_query($sql);
+
+ $global_announcements = $update_lastpost_data = array();
+ $update_lastpost_data['forum_last_post_time'] = 0;
+ $update_forum_data = array(
+ 'forum_posts' => 0,
+ 'forum_topics' => 0,
+ 'forum_topics_real' => 0,
+ );
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $global_announcements[] = (int) $row['topic_id'];
+
+ $update_forum_data['forum_posts'] += (int) $row['topic_posts'];
+ $update_forum_data['forum_topics_real']++;
+ if ($row['topic_approved'])
+ {
+ $update_forum_data['forum_topics']++;
+ }
+
+ if ($update_lastpost_data['forum_last_post_time'] < $row['topic_last_post_time'])
+ {
+ $update_lastpost_data = array(
+ 'forum_last_post_id' => (int) $row['topic_last_post_id'],
+ 'forum_last_post_subject' => $row['topic_last_post_subject'],
+ 'forum_last_post_time' => (int) $row['topic_last_post_time'],
+ 'forum_last_poster_id' => (int) $row['topic_last_poster_id'],
+ 'forum_last_poster_name' => $row['topic_last_poster_name'],
+ 'forum_last_poster_colour' => $row['topic_last_poster_colour'],
+ );
+ }
+ }
+ $this->db->sql_freeresult($result);
+
+ if (!empty($global_announcements))
+ {
+ // Update the post/topic-count for the forum and the last-post if needed
+ $sql = 'SELECT forum_id
+ FROM ' . FORUMS_TABLE . '
+ WHERE forum_type = ' . FORUM_POST;
+ $result = $this->db->sql_query_limit($sql, 1);
+ $ga_forum_id = $this->db->sql_fetchfield('forum_id');
+ $this->db->sql_freeresult($result);
+
+ $sql = 'SELECT forum_last_post_time
+ FROM ' . FORUMS_TABLE . '
+ WHERE forum_id = ' . $ga_forum_id;
+ $result = $this->db->sql_query($sql);
+ $lastpost = (int) $this->db->sql_fetchfield('forum_last_post_time');
+ $this->db->sql_freeresult($result);
+
+ $sql_update = 'forum_posts = forum_posts + ' . $update_forum_data['forum_posts'] . ', ';
+ $sql_update .= 'forum_topics_real = forum_topics_real + ' . $update_forum_data['forum_topics_real'] . ', ';
+ $sql_update .= 'forum_topics = forum_topics + ' . $update_forum_data['forum_topics'];
+ if ($lastpost < $update_lastpost_data['forum_last_post_time'])
+ {
+ $sql_update .= ', ' . $this->db->sql_build_array('UPDATE', $update_lastpost_data);
+ }
+
+ $sql = 'UPDATE ' . FORUMS_TABLE . '
+ SET ' . $sql_update . '
+ WHERE forum_id = ' . $ga_forum_id;
+ $this->sql_query($sql);
+
+ // Update some forum_ids
+ $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE);
+ foreach ($table_ary as $table)
+ {
+ $sql = "UPDATE $table
+ SET forum_id = $ga_forum_id
+ WHERE " . $this->db->sql_in_set('topic_id', $global_announcements);
+ $this->sql_query($sql);
+ }
+ unset($table_ary);
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/310/extensions.php b/phpBB/includes/db/migration/data/310/extensions.php
new file mode 100644
index 0000000000..6a9caa1cfc
--- /dev/null
+++ b/phpBB/includes/db/migration/data/310/extensions.php
@@ -0,0 +1,69 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_310_extensions extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return $this->db_tools->sql_table_exists($this->table_prefix . 'ext');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_11');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_tables' => array(
+ $this->table_prefix . 'ext' => array(
+ 'COLUMNS' => array(
+ 'ext_name' => array('VCHAR', ''),
+ 'ext_active' => array('BOOL', 0),
+ 'ext_state' => array('TEXT', ''),
+ ),
+ 'KEYS' => array(
+ 'ext_name' => array('UNIQUE', 'ext_name'),
+ ),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_tables' => array(
+ $this->table_prefix . 'ext',
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ // Module will be renamed later
+ array('module.add', array(
+ 'acp',
+ 'ACP_CAT_STYLES',
+ 'ACP_EXTENSION_MANAGEMENT'
+ )),
+ array('module.add', array(
+ 'acp',
+ 'ACP_EXTENSION_MANAGEMENT',
+ array(
+ 'module_basename' => 'acp_extensions',
+ 'modes' => array('main'),
+ ),
+ )),
+ array('permission.add', array('a_extensions', true, 'a_styles')),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/310/notifications.php b/phpBB/includes/db/migration/data/310/notifications.php
new file mode 100644
index 0000000000..82bfd4cb2d
--- /dev/null
+++ b/phpBB/includes/db/migration/data/310/notifications.php
@@ -0,0 +1,160 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_310_notifications extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return $this->db_tools->sql_table_exists($this->table_prefix . 'notifications');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_310_dev');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_tables' => array(
+ $this->table_prefix . 'notification_types' => array(
+ 'COLUMNS' => array(
+ 'notification_type' => array('VCHAR:255', ''),
+ 'notification_type_enabled' => array('BOOL', 1),
+ ),
+ 'PRIMARY_KEY' => array('notification_type', 'notification_type_enabled'),
+ ),
+ $this->table_prefix . 'notifications' => array(
+ 'COLUMNS' => array(
+ 'notification_id' => array('UINT', NULL, 'auto_increment'),
+ 'item_type' => array('VCHAR:255', ''),
+ 'item_id' => array('UINT', 0),
+ 'item_parent_id' => array('UINT', 0),
+ 'user_id' => array('UINT', 0),
+ 'notification_read' => array('BOOL', 0),
+ 'notification_time' => array('TIMESTAMP', 1),
+ 'notification_data' => array('TEXT_UNI', ''),
+ ),
+ 'PRIMARY_KEY' => 'notification_id',
+ 'KEYS' => array(
+ 'item_ident' => array('INDEX', array('item_type', 'item_id')),
+ 'user' => array('INDEX', array('user_id', 'notification_read')),
+ ),
+ ),
+ $this->table_prefix . 'user_notifications' => array(
+ 'COLUMNS' => array(
+ 'item_type' => array('VCHAR:255', ''),
+ 'item_id' => array('UINT', 0),
+ 'user_id' => array('UINT', 0),
+ 'method' => array('VCHAR:255', ''),
+ 'notify' => array('BOOL', 1),
+ ),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_tables' => array(
+ $this->table_prefix . 'notification_types',
+ $this->table_prefix . 'notifications',
+ $this->table_prefix . 'user_notifications',
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('module.add', array(
+ 'ucp',
+ 'UCP_MAIN',
+ array(
+ 'module_basename' => 'ucp_notifications',
+ 'modes' => array('notification_list'),
+ ),
+ )),
+ array('module.add', array(
+ 'ucp',
+ 'UCP_PREFS',
+ array(
+ 'module_basename' => 'ucp_notifications',
+ 'modes' => array('notification_options'),
+ ),
+ )),
+ array('config.add', array('load_notifications', 1)),
+ array('custom', array(array($this, 'convert_notifications'))),
+ );
+ }
+
+ public function convert_notifications()
+ {
+ $convert_notifications = array(
+ array(
+ 'check' => ($this->config['allow_topic_notify']),
+ 'item_type' => 'post',
+ ),
+ array(
+ 'check' => ($this->config['allow_forum_notify']),
+ 'item_type' => 'topic',
+ ),
+ array(
+ 'check' => ($this->config['allow_bookmarks']),
+ 'item_type' => 'bookmark',
+ ),
+ array(
+ 'check' => ($this->config['allow_privmsg']),
+ 'item_type' => 'pm',
+ ),
+ );
+
+ foreach ($convert_notifications as $convert_data)
+ {
+ if ($convert_data['check'])
+ {
+ $sql = 'SELECT user_id, user_notify_type
+ FROM ' . USERS_TABLE . '
+ WHERE user_notify = 1';
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $this->sql_query('INSERT INTO ' . $this->table_prefix . 'user_notifications ' . $this->db->sql_build_array('INSERT', array(
+ 'item_type' => $convert_data['item_type'],
+ 'item_id' => 0,
+ 'user_id' => $row['user_id'],
+ 'method' => '',
+ )));
+
+ if ($row['user_notify_type'] == NOTIFY_EMAIL || $row['user_notify_type'] == NOTIFY_BOTH)
+ {
+ $this->sql_query('INSERT INTO ' . $this->table_prefix . 'user_notifications ' . $this->db->sql_build_array('INSERT', array(
+ 'item_type' => $convert_data['item_type'],
+ 'item_id' => 0,
+ 'user_id' => $row['user_id'],
+ 'method' => 'email',
+ )));
+ }
+
+ if ($row['user_notify_type'] == NOTIFY_IM || $row['user_notify_type'] == NOTIFY_BOTH)
+ {
+ $this->sql_query('INSERT INTO ' . $this->table_prefix . 'user_notifications ' . $this->db->sql_build_array('INSERT', array(
+ 'item_type' => $convert_data['item_type'],
+ 'item_id' => 0,
+ 'user_id' => $row['user_id'],
+ 'method' => 'jabber',
+ )));
+ }
+ }
+ $this->db->sql_freeresult($result);
+ }
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/310/reported_posts_display.php b/phpBB/includes/db/migration/data/310/reported_posts_display.php
new file mode 100644
index 0000000000..80a0a0e43f
--- /dev/null
+++ b/phpBB/includes/db/migration/data/310/reported_posts_display.php
@@ -0,0 +1,47 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_310_reported_posts_display extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return $this->db_tools->sql_column_exists($this->table_prefix . 'reports', 'reported_post_enable_bbcode');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_11');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'add_columns' => array(
+ $this->table_prefix . 'reports' => array(
+ 'reported_post_enable_bbcode' => array('BOOL', 1),
+ 'reported_post_enable_smilies' => array('BOOL', 1),
+ 'reported_post_enable_magic_url' => array('BOOL', 1),
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'drop_columns' => array(
+ $this->table_prefix . 'reports' => array(
+ 'reported_post_enable_bbcode',
+ 'reported_post_enable_smilies',
+ 'reported_post_enable_magic_url',
+ ),
+ ),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/data/310/style_update_p1.php b/phpBB/includes/db/migration/data/310/style_update_p1.php
new file mode 100644
index 0000000000..e324ce7f24
--- /dev/null
+++ b/phpBB/includes/db/migration/data/310/style_update_p1.php
@@ -0,0 +1,157 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_310_style_update_p1 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return !$this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_11');
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array($this, 'styles_update'))),
+ );
+ }
+
+ public function styles_update()
+ {
+ // Get list of valid 3.1 styles
+ $available_styles = array('prosilver');
+
+ $iterator = new DirectoryIterator($this->phpbb_root_path . 'styles');
+ $skip_dirs = array('.', '..', 'prosilver');
+ foreach ($iterator as $fileinfo)
+ {
+ if ($fileinfo->isDir() && !in_array($fileinfo->getFilename(), $skip_dirs) && file_exists($fileinfo->getPathname() . '/style.cfg'))
+ {
+ $style_cfg = parse_cfg_file($fileinfo->getPathname() . '/style.cfg');
+ if (isset($style_cfg['phpbb_version']) && version_compare($style_cfg['phpbb_version'], '3.1.0-dev', '>='))
+ {
+ // 3.1 style
+ $available_styles[] = $fileinfo->getFilename();
+ }
+ }
+ }
+
+ // Get all installed styles
+ if ($this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'))
+ {
+ $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id, i.imageset_path
+ FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . 'styles_theme c, ' . $this->table_prefix . "styles_imageset i
+ WHERE t.template_id = s.template_id
+ AND c.theme_id = s.theme_id
+ AND i.imageset_id = s.imageset_id";
+ }
+ else
+ {
+ $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id
+ FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . "stles_theme c
+ WHERE t.template_id = s.template_id
+ AND c.theme_id = s.theme_id";
+ }
+ $result = $this->db->sql_query($sql);
+
+ $styles = array();
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $styles[] = $row;
+ }
+ $this->db->sql_freeresult($result);
+
+ // Decide which styles to keep, all others will be deleted
+ $valid_styles = array();
+ foreach ($styles as $style_row)
+ {
+ if (
+ // Delete styles with parent style (not supported yet)
+ $style_row['template_inherits_id'] == 0 &&
+ // Check if components match
+ $style_row['template_path'] == $style_row['theme_path'] && (!isset($style_row['imageset_path']) || $style_row['template_path'] == $style_row['imageset_path']) &&
+ // Check if components are valid
+ in_array($style_row['template_path'], $available_styles)
+ )
+ {
+ // Valid style. Keep it
+ $sql_ary = array(
+ 'style_path' => $style_row['template_path'],
+ 'bbcode_bitfield' => $style_row['bbcode_bitfield'],
+ 'style_parent_id' => 0,
+ 'style_parent_tree' => '',
+ );
+ $this->sql_query('UPDATE ' . STYLES_TABLE . '
+ SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . '
+ WHERE style_id = ' . $style_row['style_id']);
+ $valid_styles[] = (int) $style_row['style_id'];
+ }
+ }
+
+ // Remove old entries from styles table
+ if (!sizeof($valid_styles))
+ {
+ // No valid styles: remove everything and add prosilver
+ $this->sql_query('DELETE FROM ' . STYLES_TABLE, $errored, $error_ary);
+
+ $sql_ary = array(
+ 'style_name' => 'prosilver',
+ 'style_copyright' => '&copy; phpBB Group',
+ 'style_active' => 1,
+ 'style_path' => 'prosilver',
+ 'bbcode_bitfield' => 'lNg=',
+ 'style_parent_id' => 0,
+ 'style_parent_tree' => '',
+
+ // Will be removed in the next step
+ 'imageset_id' => 0,
+ 'template_id' => 0,
+ 'theme_id' => 0,
+ );
+
+ $sql = 'INSERT INTO ' . STYLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary);
+ $this->sql_query($sql);
+
+ $sql = 'SELECT style_id
+ FROM ' . $table . "
+ WHERE style_name = 'prosilver'";
+ $result = $this->sql_query($sql);
+ $default_style = $this->db->sql_fetchfield($result);
+ $this->db->sql_freeresult($result);
+
+ set_config('default_style', $default_style);
+
+ $sql = 'UPDATE ' . USERS_TABLE . ' SET user_style = 0';
+ $this->sql_query($sql);
+ }
+ else
+ {
+ // There are valid styles in styles table. Remove styles that are outdated
+ $this->sql_query('DELETE FROM ' . STYLES_TABLE . '
+ WHERE ' . $this->db->sql_in_set('style_id', $valid_styles, true));
+
+ // Change default style
+ if (!in_array($this->config['default_style'], $valid_styles))
+ {
+ $this->sql_query('UPDATE ' . CONFIG_TABLE . "
+ SET config_value = '" . $valid_styles[0] . "'
+ WHERE config_name = 'default_style'");
+ }
+
+ // Reset styles for users
+ $this->sql_query('UPDATE ' . USERS_TABLE . '
+ SET user_style = 0
+ WHERE ' . $this->db->sql_in_set('user_style', $valid_styles, true));
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/data/310/style_update_p2.php b/phpBB/includes/db/migration/data/310/style_update_p2.php
new file mode 100644
index 0000000000..7b10518a66
--- /dev/null
+++ b/phpBB/includes/db/migration/data/310/style_update_p2.php
@@ -0,0 +1,129 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_310_style_update_p2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return !$this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_310_style_update_p1');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'drop_columns' => array(
+ $this->table_prefix . 'styles' => array(
+ 'imageset_id',
+ 'template_id',
+ 'theme_id',
+ ),
+ ),
+
+ 'drop_tables' => array(
+ $this->table_prefix . 'styles_imageset',
+ $this->table_prefix . 'styles_imageset_data',
+ $this->table_prefix . 'styles_template',
+ $this->table_prefix . 'styles_template_data',
+ $this->table_prefix . 'styles_theme',
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'add_columns' => array(
+ $this->table_prefix . 'styles' => array(
+ 'imageset_id' => array('UINT', 0),
+ 'template_id' => array('UINT', 0),
+ 'theme_id' => array('UINT', 0),
+ ),
+ ),
+
+ 'add_tables' => array(
+ $this->table_prefix . 'styles_imageset' => array(
+ 'COLUMNS' => array(
+ 'imageset_id' => array('UINT', NULL, 'auto_increment'),
+ 'imageset_name' => array('VCHAR_UNI:255', ''),
+ 'imageset_copyright' => array('VCHAR_UNI', ''),
+ 'imageset_path' => array('VCHAR:100', ''),
+ ),
+ 'PRIMARY_KEY' => 'imageset_id',
+ 'KEYS' => array(
+ 'imgset_nm' => array('UNIQUE', 'imageset_name'),
+ ),
+ ),
+ $this->table_prefix . 'styles_imageset_data' => array(
+ 'COLUMNS' => array(
+ 'image_id' => array('UINT', NULL, 'auto_increment'),
+ 'image_name' => array('VCHAR:200', ''),
+ 'image_filename' => array('VCHAR:200', ''),
+ 'image_lang' => array('VCHAR:30', ''),
+ 'image_height' => array('USINT', 0),
+ 'image_width' => array('USINT', 0),
+ 'imageset_id' => array('UINT', 0),
+ ),
+ 'PRIMARY_KEY' => 'image_id',
+ 'KEYS' => array(
+ 'i_d' => array('INDEX', 'imageset_id'),
+ ),
+ ),
+ $this->table_prefix . 'styles_template' => array(
+ 'COLUMNS' => array(
+ 'template_id' => array('UINT', NULL, 'auto_increment'),
+ 'template_name' => array('VCHAR_UNI:255', ''),
+ 'template_copyright' => array('VCHAR_UNI', ''),
+ 'template_path' => array('VCHAR:100', ''),
+ 'bbcode_bitfield' => array('VCHAR:255', 'kNg='),
+ 'template_storedb' => array('BOOL', 0),
+ 'template_inherits_id' => array('UINT:4', 0),
+ 'template_inherit_path' => array('VCHAR', ''),
+ ),
+ 'PRIMARY_KEY' => 'template_id',
+ 'KEYS' => array(
+ 'tmplte_nm' => array('UNIQUE', 'template_name'),
+ ),
+ ),
+ $this->table_prefix . 'styles_template_data' => array(
+ 'COLUMNS' => array(
+ 'template_id' => array('UINT', 0),
+ 'template_filename' => array('VCHAR:100', ''),
+ 'template_included' => array('TEXT', ''),
+ 'template_mtime' => array('TIMESTAMP', 0),
+ 'template_data' => array('MTEXT_UNI', ''),
+ ),
+ 'KEYS' => array(
+ 'tid' => array('INDEX', 'template_id'),
+ 'tfn' => array('INDEX', 'template_filename'),
+ ),
+ ),
+ $this->table_prefix . 'styles_theme' => array(
+ 'COLUMNS' => array(
+ 'theme_id' => array('UINT', NULL, 'auto_increment'),
+ 'theme_name' => array('VCHAR_UNI:255', ''),
+ 'theme_copyright' => array('VCHAR_UNI', ''),
+ 'theme_path' => array('VCHAR:100', ''),
+ 'theme_storedb' => array('BOOL', 0),
+ 'theme_mtime' => array('TIMESTAMP', 0),
+ 'theme_data' => array('MTEXT_UNI', ''),
+ ),
+ 'PRIMARY_KEY' => 'theme_id',
+ 'KEYS' => array(
+ 'theme_name' => array('UNIQUE', 'theme_name'),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/phpBB/includes/update_helpers.php b/phpBB/includes/db/migration/data/310/timezone.php
index 69d678b2f8..6e50cbe45f 100644
--- a/phpBB/includes/update_helpers.php
+++ b/phpBB/includes/db/migration/data/310/timezone.php
@@ -1,16 +1,67 @@
<?php
/**
*
-* @package phpBB3
+* @package migration
* @copyright (c) 2012 phpBB Group
-* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
*/
-/**
-* phpBB Update Helpers
-*/
-class phpbb_update_helpers
+class phpbb_db_migration_data_310_timezone extends phpbb_db_migration
{
+ public function effectively_installed()
+ {
+ return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_30x_3_0_11');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'change_columns' => array(
+ $this->table_prefix . 'users' => array(
+ 'user_timezone' => array('VCHAR:100', ''),
+ ),
+ ),
+ );
+ }
+
+ public function update_data()
+ {
+ return array(
+ array('custom', array(array($this, 'update_timezones'))),
+ );
+ }
+
+ public function update_timezones()
+ {
+ // Update user timezones
+ $sql = 'SELECT user_dst, user_timezone
+ FROM ' . $this->table_prefix . 'users
+ GROUP BY user_timezone, user_dst';
+ $result = $this->db->sql_query($sql);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $sql = 'UPDATE ' . $this->table_prefix . "users
+ SET user_timezone = '" . $this->db->sql_escape($this->convert_phpbb30_timezone($row['user_timezone'], $row['user_dst'])) . "'
+ WHERE user_timezone = '" . $this->db->sql_escape($row['user_timezone']) . "'
+ AND user_dst = " . (int) $row['user_dst'];
+ $this->sql_query($sql);
+ }
+ $this->db->sql_freeresult($result);
+
+ // Update board default timezone
+ $sql = 'UPDATE ' . $this->table_prefix . "config
+ SET config_value = '" . $this->convert_phpbb30_timezone($this->config['board_timezone'], $this->config['board_dst']) . "'
+ WHERE config_name = 'board_timezone'";
+ $this->sql_query($sql);
+ }
+
/**
* Determine the new timezone for a given phpBB 3.0 timezone and
* "Daylight Saving Time" option
@@ -19,7 +70,7 @@ class phpbb_update_helpers
* @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)
+ public function convert_phpbb30_timezone($timezone, $dst)
{
$offset = $timezone + $dst;
diff --git a/phpBB/includes/db/migration/data/310/timezone_p2.php b/phpBB/includes/db/migration/data/310/timezone_p2.php
new file mode 100644
index 0000000000..113b979e4f
--- /dev/null
+++ b/phpBB/includes/db/migration/data/310/timezone_p2.php
@@ -0,0 +1,43 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+class phpbb_db_migration_data_310_timezone_p2 extends phpbb_db_migration
+{
+ public function effectively_installed()
+ {
+ return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst');
+ }
+
+ static public function depends_on()
+ {
+ return array('phpbb_db_migration_data_310_timezone');
+ }
+
+ public function update_schema()
+ {
+ return array(
+ 'drop_columns' => array(
+ $this->table_prefix . 'users' => array(
+ 'user_dst',
+ ),
+ ),
+ );
+ }
+
+ public function revert_schema()
+ {
+ return array(
+ 'add_columns' => array(
+ $this->table_prefix . 'users' => array(
+ 'user_dst' => array('BOOL', 0),
+ ),
+ ),
+ );
+ }
+}
diff --git a/phpBB/includes/db/migration/exception.php b/phpBB/includes/db/migration/exception.php
new file mode 100644
index 0000000000..e84330dd71
--- /dev/null
+++ b/phpBB/includes/db/migration/exception.php
@@ -0,0 +1,79 @@
+<?php
+/**
+*
+* @package db
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* The migrator is responsible for applying new migrations in the correct order.
+*
+* @package db
+*/
+class phpbb_db_migration_exception extends \Exception
+{
+ /**
+ * Extra parameters sent to exception to aid in debugging
+ * @var array
+ */
+ protected $parameters;
+
+ /**
+ * Throw an exception.
+ *
+ * First argument is the error message.
+ * Additional arguments will be output with the error message.
+ */
+ public function __construct()
+ {
+ $parameters = func_get_args();
+ $message = array_shift($parameters);
+ parent::__construct($message);
+
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Output the error as a string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->message . ': ' . var_export($this->parameters, true);
+ }
+
+ /**
+ * Get the parameters
+ *
+ * @return array
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * Get localised message (with $user->lang())
+ *
+ * @param phpbb_user $user
+ * @return string
+ */
+ public function getLocalisedMessage(phpbb_user $user)
+ {
+ $parameters = $this->getParameters();
+ array_unshift($parameters, $this->getMessage());
+
+ return call_user_func_array(array($user, 'lang'), $parameters);
+ }
+}
diff --git a/phpBB/includes/db/migration/migration.php b/phpBB/includes/db/migration/migration.php
new file mode 100644
index 0000000000..5f14a6953c
--- /dev/null
+++ b/phpBB/includes/db/migration/migration.php
@@ -0,0 +1,190 @@
+<?php
+/**
+*
+* @package db
+* @copyright (c) 2011 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Abstract base class for database migrations
+*
+* Each migration consists of a set of schema and data changes to be implemented
+* in a subclass. This class provides various utility methods to simplify editing
+* a phpBB.
+*
+* @package db
+*/
+abstract class phpbb_db_migration
+{
+ /** @var phpbb_config */
+ protected $config;
+
+ /** @var phpbb_db_driver */
+ protected $db;
+
+ /** @var phpbb_db_tools */
+ protected $db_tools;
+
+ /** @var string */
+ protected $table_prefix;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /** @var array Errors, if any occured */
+ protected $errors;
+
+ /** @var array List of queries executed through $this->sql_query() */
+ protected $queries = array();
+
+ /**
+ * Constructor
+ *
+ * @param phpbb_config $config
+ * @param phpbb_db_driver $db
+ * @param phpbb_db_tools $db_tools
+ * @param string $phpbb_root_path
+ * @param string $php_ext
+ * @param string $table_prefix
+ */
+ public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix)
+ {
+ $this->config = $config;
+ $this->db = $db;
+ $this->db_tools = $db_tools;
+ $this->table_prefix = $table_prefix;
+
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+
+ $this->errors = array();
+ }
+
+ /**
+ * Defines other migrations to be applied first
+ *
+ * @return array An array of migration class names
+ */
+ static public function depends_on()
+ {
+ return array();
+ }
+
+ /**
+ * Allows you to check if the migration is effectively installed (entirely optional)
+ *
+ * This is checked when a migration is installed. If true is returned, the migration will be set as
+ * installed without performing the database changes.
+ * This function is intended to help moving to migrations from a previous database updater, where some
+ * migrations may have been installed already even though they are not yet listed in the migrations table.
+ *
+ * @return bool True if this migration is installed, False if this migration is not installed (checked on install)
+ */
+ public function effectively_installed()
+ {
+ return false;
+ }
+
+ /**
+ * Updates the database schema by providing a set of change instructions
+ *
+ * @return array Array of schema changes (compatible with db_tools->perform_schema_changes())
+ */
+ public function update_schema()
+ {
+ return array();
+ }
+
+ /**
+ * Reverts the database schema by providing a set of change instructions
+ *
+ * @return array Array of schema changes (compatible with db_tools->perform_schema_changes())
+ */
+ public function revert_schema()
+ {
+ return array();
+ }
+
+ /**
+ * Updates data by returning a list of instructions to be executed
+ *
+ * @return array Array of data update instructions
+ */
+ public function update_data()
+ {
+ return array();
+ }
+
+ /**
+ * Reverts data by returning a list of instructions to be executed
+ *
+ * @return array Array of data instructions that will be performed on revert
+ * NOTE: calls to tools (such as config.add) are automatically reverted when
+ * possible, so you should not attempt to revert those, this is mostly for
+ * otherwise unrevertable calls (custom functions for example)
+ */
+ public function revert_data()
+ {
+ return array();
+ }
+
+ /**
+ * Wrapper for running queries to generate user feedback on updates
+ *
+ * @param string $sql SQL query to run on the database
+ * @return mixed Query result from db->sql_query()
+ */
+ protected function sql_query($sql)
+ {
+ $this->queries[] = $sql;
+
+ $this->db->sql_return_on_error(true);
+
+ if ($sql === 'begin')
+ {
+ $result = $this->db->sql_transaction('begin');
+ }
+ else if ($sql === 'commit')
+ {
+ $result = $this->db->sql_transaction('commit');
+ }
+ else
+ {
+ $result = $this->db->sql_query($sql);
+ if ($this->db->sql_error_triggered)
+ {
+ $this->errors[] = array(
+ 'sql' => $this->db->sql_error_sql,
+ 'code' => $this->db->sql_error_returned,
+ );
+ }
+ }
+
+ $this->db->sql_return_on_error(false);
+
+ return $result;
+ }
+
+ /**
+ * Get the list of queries run
+ *
+ * @return array
+ */
+ public function get_queries()
+ {
+ return $this->queries;
+ }
+}
diff --git a/phpBB/includes/db/migration/tool/config.php b/phpBB/includes/db/migration/tool/config.php
new file mode 100644
index 0000000000..458a25fb66
--- /dev/null
+++ b/phpBB/includes/db/migration/tool/config.php
@@ -0,0 +1,150 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* Migration config tool
+*
+* @package db
+*/
+class phpbb_db_migration_tool_config implements phpbb_db_migration_tool_interface
+{
+ /** @var phpbb_config */
+ protected $config;
+
+ /**
+ * Constructor
+ *
+ * @param phpbb_config $config
+ */
+ public function __construct(phpbb_config $config)
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get_name()
+ {
+ return 'config';
+ }
+
+ /**
+ * Add a config setting.
+ *
+ * @param string $config_name The name of the config setting
+ * you would like to add
+ * @param mixed $config_value The value of the config setting
+ * @param bool $is_dynamic True if it is dynamic (changes very often)
+ * and should not be stored in the cache, false if not.
+ * @return null
+ */
+ public function add($config_name, $config_value, $is_dynamic = false)
+ {
+ if (isset($this->config[$config_name]))
+ {
+ throw new phpbb_db_migration_exception('CONFIG_ALREADY_EXIST', $config_name);
+ }
+
+ $this->config->set($config_name, $config_value, !$is_dynamic);
+ }
+
+ /**
+ * Update an existing config setting.
+ *
+ * @param string $config_name The name of the config setting you would
+ * like to update
+ * @param mixed $config_value The value of the config setting
+ * @return null
+ */
+ public function update($config_name, $config_value)
+ {
+ if (!isset($this->config[$config_name]))
+ {
+ throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name);
+ }
+
+ $this->config->set($config_name, $config_value);
+ }
+
+ /**
+ * Update a config setting if the first argument equal to the
+ * current config value
+ *
+ * @param string $compare If equal to the current config value, will be
+ * updated to the new config value, otherwise not
+ * @param string $config_name The name of the config setting you would
+ * like to update
+ * @param mixed $config_value The value of the config setting
+ * @return null
+ */
+ public function update_if_equals($compare, $config_name, $config_value)
+ {
+ if (!isset($this->config[$config_name]))
+ {
+ throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name);
+ }
+
+ $this->config->set_atomic($config_name, $compare, $config_value);
+ }
+
+ /**
+ * Remove an existing config setting.
+ *
+ * @param string $config_name The name of the config setting you would
+ * like to remove
+ * @return null
+ */
+ public function remove($config_name)
+ {
+ if (!isset($this->config[$config_name]))
+ {
+ throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name);
+ }
+
+ $this->config->delete($config_name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reverse()
+ {
+ $arguments = func_get_args();
+ $original_call = array_shift($arguments);
+
+ $call = false;
+ switch ($original_call)
+ {
+ case 'add':
+ $call = 'remove';
+ break;
+
+ case 'remove':
+ $call = 'add';
+ break;
+
+ case 'update_if_equals':
+ $call = 'update_if_equals';
+
+ // Set to the original value if the current value is what we compared to originally
+ $arguments = array(
+ $arguments[2],
+ $arguments[1],
+ $arguments[0],
+ );
+ break;
+ }
+
+ if ($call)
+ {
+ return call_user_func_array(array(&$this, $call), $arguments);
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/tool/interface.php b/phpBB/includes/db/migration/tool/interface.php
new file mode 100644
index 0000000000..ced53b2023
--- /dev/null
+++ b/phpBB/includes/db/migration/tool/interface.php
@@ -0,0 +1,33 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* Migration tool interface
+*
+* @package db
+*/
+interface phpbb_db_migration_tool_interface
+{
+ /**
+ * Retrieve a short name used for commands in migrations.
+ *
+ * @return string short name
+ */
+ public function get_name();
+
+ /**
+ * Reverse an original install action
+ *
+ * First argument is the original call to the class (e.g. add, remove)
+ * After the first argument, send the original arguments to the function in the original call
+ *
+ * @return null
+ */
+ public function reverse();
+}
diff --git a/phpBB/includes/db/migration/tool/module.php b/phpBB/includes/db/migration/tool/module.php
new file mode 100644
index 0000000000..ad94c5aadb
--- /dev/null
+++ b/phpBB/includes/db/migration/tool/module.php
@@ -0,0 +1,502 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* Migration module management tool
+*
+* @package db
+*/
+class phpbb_db_migration_tool_module implements phpbb_db_migration_tool_interface
+{
+ /** @var phpbb_cache_service */
+ protected $cache;
+
+ /** @var dbal */
+ protected $db;
+
+ /** @var phpbb_user */
+ protected $user;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /** @var string */
+ protected $modules_table;
+
+ /**
+ * Constructor
+ *
+ * @param phpbb_db_driver $db
+ * @param mixed $cache
+ * @param phpbb_user $user
+ * @param string $phpbb_root_path
+ * @param string $php_ext
+ * @param string $modules_table
+ */
+ public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_user $user, $phpbb_root_path, $php_ext, $modules_table)
+ {
+ $this->db = $db;
+ $this->cache = $cache;
+ $this->user = $user;
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+ $this->modules_table = $modules_table;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get_name()
+ {
+ return 'module';
+ }
+
+ /**
+ * Module Exists
+ *
+ * Check if a module exists
+ *
+ * @param string $class The module class(acp|mcp|ucp)
+ * @param int|string|bool $parent The parent module_id|module_langname (0 for no parent).
+ * Use false to ignore the parent check and check class wide.
+ * @param int|string $module The module_id|module_langname you would like to
+ * check for to see if it exists
+ * @return bool true/false if module exists
+ */
+ public function exists($class, $parent, $module)
+ {
+ // the main root directory should return true
+ if (!$module)
+ {
+ return true;
+ }
+
+ $parent_sql = '';
+ if ($parent !== false)
+ {
+ // Allows '' to be sent as 0
+ $parent = $parent ?: 0;
+
+ if (!is_numeric($parent))
+ {
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_langname = '" . $this->db->sql_escape($parent) . "'
+ AND module_class = '" . $this->db->sql_escape($class) . "'";
+ $result = $this->db->sql_query($sql);
+ $module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ if (!$module_id)
+ {
+ return false;
+ }
+
+ $parent_sql = 'AND parent_id = ' . (int) $module_id;
+ }
+ else
+ {
+ $parent_sql = 'AND parent_id = ' . (int) $parent;
+ }
+ }
+
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ $parent_sql
+ AND " . ((is_numeric($module)) ? 'module_id = ' . (int) $module : "module_langname = '" . $this->db->sql_escape($module) . "'");
+ $result = $this->db->sql_query($sql);
+ $module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ if ($module_id)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Module Add
+ *
+ * Add a new module
+ *
+ * @param string $class The module class(acp|mcp|ucp)
+ * @param int|string $parent The parent module_id|module_langname (0 for no parent)
+ * @param array $data an array of the data on the new module.
+ * This can be setup in two different ways.
+ * 1. The "manual" way. For inserting a category or one at a time.
+ * It will be merged with the base array shown a bit below,
+ * but at the least requires 'module_langname' to be sent, and,
+ * if you want to create a module (instead of just a category) you must
+ * send module_basename and module_mode.
+ * array(
+ * 'module_enabled' => 1,
+ * 'module_display' => 1,
+ * 'module_basename' => '',
+ * 'module_class' => $class,
+ * 'parent_id' => (int) $parent,
+ * 'module_langname' => '',
+ * 'module_mode' => '',
+ * 'module_auth' => '',
+ * )
+ * 2. The "automatic" way. For inserting multiple at a time based on the
+ * specs in the info file for the module(s). For this to work the
+ * modules must be correctly setup in the info file.
+ * An example follows (this would insert the settings, log, and flag
+ * modes from the includes/acp/info/acp_asacp.php file):
+ * array(
+ * 'module_basename' => 'asacp',
+ * 'modes' => array('settings', 'log', 'flag'),
+ * )
+ * Optionally you may not send 'modes' and it will insert all of the
+ * modules in that info file.
+ * @param string|bool $include_path If you would like to use a custom include
+ * path, specify that here
+ * @return null
+ */
+ public function add($class, $parent = 0, $data = array(), $include_path = false)
+ {
+ // Allows '' to be sent as 0
+ $parent = $parent ?: 0;
+
+ // allow sending the name as a string in $data to create a category
+ if (!is_array($data))
+ {
+ $data = array('module_langname' => $data);
+ }
+
+ if (!isset($data['module_langname']))
+ {
+ // The "automatic" way
+ $basename = (isset($data['module_basename'])) ? $data['module_basename'] : '';
+ $basename = str_replace(array('/', '\\'), '', $basename);
+ $class = str_replace(array('/', '\\'), '', $class);
+
+ $module = $this->get_module_info($class, $basename);
+
+ $result = '';
+ foreach ($module['modes'] as $mode => $module_info)
+ {
+ if (!isset($data['modes']) || in_array($mode, $data['modes']))
+ {
+ $new_module = array(
+ 'module_basename' => $basename,
+ 'module_langname' => $module_info['title'],
+ 'module_mode' => $mode,
+ 'module_auth' => $module_info['auth'],
+ 'module_display' => (isset($module_info['display'])) ? $module_info['display'] : true,
+ 'before' => (isset($module_info['before'])) ? $module_info['before'] : false,
+ 'after' => (isset($module_info['after'])) ? $module_info['after'] : false,
+ );
+
+ // Run the "manual" way with the data we've collected.
+ $this->add($class, $parent, $new_module);
+ }
+ }
+
+ return;
+ }
+
+ // The "manual" way
+ $module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']);
+ add_log('admin', 'LOG_MODULE_ADD', $module_log_name);
+
+ if (!is_numeric($parent))
+ {
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_langname = '" . $this->db->sql_escape($parent) . "'
+ AND module_class = '" . $this->db->sql_escape($class) . "'";
+ $result = $this->db->sql_query($sql);
+ $module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ if (!$module_id)
+ {
+ throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent);
+ }
+
+ $parent = $data['parent_id'] = $module_id;
+ }
+ else if (!$this->exists($class, false, $parent))
+ {
+ throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent);
+ }
+
+ if ($this->exists($class, $parent, $data['module_langname']))
+ {
+ throw new phpbb_db_migration_exception('MODULE_ALREADY_EXIST', $data['module_langname']);
+ }
+
+ if (!class_exists('acp_modules'))
+ {
+ include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext);
+ $this->user->add_lang('acp/modules');
+ }
+ $acp_modules = new acp_modules();
+
+ $module_data = array(
+ 'module_enabled' => (isset($data['module_enabled'])) ? $data['module_enabled'] : 1,
+ 'module_display' => (isset($data['module_display'])) ? $data['module_display'] : 1,
+ 'module_basename' => (isset($data['module_basename'])) ? $data['module_basename'] : '',
+ 'module_class' => $class,
+ 'parent_id' => (int) $parent,
+ 'module_langname' => (isset($data['module_langname'])) ? $data['module_langname'] : '',
+ 'module_mode' => (isset($data['module_mode'])) ? $data['module_mode'] : '',
+ 'module_auth' => (isset($data['module_auth'])) ? $data['module_auth'] : '',
+ );
+ $result = $acp_modules->update_module_data($module_data, true);
+
+ // update_module_data can either return a string or an empty array...
+ if (is_string($result))
+ {
+ // Error
+ throw new phpbb_db_migration_exception('MODULE_ERROR', $result);
+ }
+ else
+ {
+ // Success
+
+ // Move the module if requested above/below an existing one
+ if (isset($data['before']) && $data['before'])
+ {
+ $sql = 'SELECT left_id
+ FROM ' . $this->modules_table . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND parent_id = " . (int) $parent . "
+ AND module_langname = '" . $this->db->sql_escape($data['before']) . "'";
+ $this->db->sql_query($sql);
+ $to_left = (int) $this->db->sql_fetchfield('left_id');
+
+ $sql = 'UPDATE ' . $this->modules_table . "
+ SET left_id = left_id + 2, right_id = right_id + 2
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND left_id >= $to_left
+ AND left_id < {$module_data['left_id']}";
+ $this->db->sql_query($sql);
+
+ $sql = 'UPDATE ' . $this->modules_table . "
+ SET left_id = $to_left, right_id = " . ($to_left + 1) . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND module_id = {$module_data['module_id']}";
+ $this->db->sql_query($sql);
+ }
+ else if (isset($data['after']) && $data['after'])
+ {
+ $sql = 'SELECT right_id
+ FROM ' . $this->modules_table . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND parent_id = " . (int) $parent . "
+ AND module_langname = '" . $this->db->sql_escape($data['after']) . "'";
+ $this->db->sql_query($sql);
+ $to_right = (int) $this->db->sql_fetchfield('right_id');
+
+ $sql = 'UPDATE ' . $this->modules_table . "
+ SET left_id = left_id + 2, right_id = right_id + 2
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND left_id >= $to_right
+ AND left_id < {$module_data['left_id']}";
+ $this->db->sql_query($sql);
+
+ $sql = 'UPDATE ' . $this->modules_table . '
+ SET left_id = ' . ($to_right + 1) . ', right_id = ' . ($to_right + 2) . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND module_id = {$module_data['module_id']}";
+ $this->db->sql_query($sql);
+ }
+ }
+
+ // Clear the Modules Cache
+ $this->cache->destroy("_modules_$class");
+ }
+
+ /**
+ * Module Remove
+ *
+ * Remove a module
+ *
+ * @param string $class The module class(acp|mcp|ucp)
+ * @param int|string|bool $parent The parent module_id|module_langname(0 for no parent).
+ * Use false to ignore the parent check and check class wide.
+ * @param int|string $module The module id|module_langname
+ * @param string|bool $include_path If you would like to use a custom include path,
+ * specify that here
+ * @return null
+ */
+ public function remove($class, $parent = 0, $module = '', $include_path = false)
+ {
+ // Imitation of module_add's "automatic" and "manual" method so the uninstaller works from the same set of instructions for umil_auto
+ if (is_array($module))
+ {
+ if (isset($module['module_langname']))
+ {
+ // Manual Method
+ return $this->remove($class, $parent, $module['module_langname'], $include_path);
+ }
+
+ // Failed.
+ if (!isset($module['module_basename']))
+ {
+ throw new phpbb_db_migration_exception('MODULE_NOT_EXIST');
+ }
+
+ // Automatic method
+ $basename = str_replace(array('/', '\\'), '', $module['module_basename']);
+ $class = str_replace(array('/', '\\'), '', $class);
+
+ $module_info = $this->get_module_info($class, $basename);
+
+ foreach ($module_info['modes'] as $mode => $info)
+ {
+ if (!isset($module['modes']) || in_array($mode, $module['modes']))
+ {
+ $this->remove($class, $parent, $info['title']);
+ }
+ }
+ }
+ else
+ {
+ if (!$this->exists($class, $parent, $module))
+ {
+ throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', ((isset($this->user->lang[$module])) ? $this->user->lang[$module] : $module));
+ }
+
+ $parent_sql = '';
+ if ($parent !== false)
+ {
+ // Allows '' to be sent as 0
+ $parent = ($parent) ?: 0;
+
+ if (!is_numeric($parent))
+ {
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_langname = '" . $this->db->sql_escape($parent) . "'
+ AND module_class = '" . $this->db->sql_escape($class) . "'";
+ $result = $this->db->sql_query($sql);
+ $module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ // we know it exists from the module_exists check
+ $parent_sql = 'AND parent_id = ' . (int) $module_id;
+ }
+ else
+ {
+ $parent_sql = 'AND parent_id = ' . (int) $parent;
+ }
+ }
+
+ $module_ids = array();
+ if (!is_numeric($module))
+ {
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_langname = '" . $this->db->sql_escape($module) . "'
+ AND module_class = '" . $this->db->sql_escape($class) . "'
+ $parent_sql";
+ $result = $this->db->sql_query($sql);
+ while ($module_id = $this->db->sql_fetchfield('module_id'))
+ {
+ $module_ids[] = (int) $module_id;
+ }
+ $this->db->sql_freeresult($result);
+
+ $module_name = $module;
+ }
+ else
+ {
+ $module = (int) $module;
+ $sql = 'SELECT module_langname
+ FROM ' . $this->modules_table . "
+ WHERE module_id = $module
+ AND module_class = '" . $this->db->sql_escape($class) . "'
+ $parent_sql";
+ $result = $this->db->sql_query($sql);
+ $module_name = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ $module_ids[] = $module;
+ }
+
+ if (!class_exists('acp_modules'))
+ {
+ include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext);
+ $this->user->add_lang('acp/modules');
+ }
+ $acp_modules = new acp_modules();
+ $acp_modules->module_class = $class;
+
+ foreach ($module_ids as $module_id)
+ {
+ $result = $acp_modules->delete_module($module_id);
+ if (!empty($result))
+ {
+ throw new phpbb_db_migration_exception('MODULE_NOT_REMOVABLE', $module_id, $result);
+ }
+ }
+
+ $this->cache->destroy("_modules_$class");
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reverse()
+ {
+ $arguments = func_get_args();
+ $original_call = array_shift($arguments);
+
+ $call = false;
+ switch ($original_call)
+ {
+ case 'add':
+ $call = 'remove';
+ break;
+
+ case 'remove':
+ $call = 'add';
+ break;
+ }
+
+ if ($call)
+ {
+ return call_user_func_array(array(&$this, $call), $arguments);
+ }
+ }
+
+ /**
+ * Wrapper for acp_modules::get_module_infos()
+ *
+ * @param string $class Module Class
+ * @param string $basename Module Basename
+ * @return array Module Information
+ */
+ protected function get_module_info($class, $basename)
+ {
+ if (!class_exists('acp_modules'))
+ {
+ include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext);
+ }
+ $acp_modules = new acp_modules();
+ $module = $acp_modules->get_module_infos($basename, $class, true);
+
+ if (empty($module))
+ {
+ throw new phpbb_db_migration_exception('MODULE_INFO_FILE_NOT_EXIST', $class, $basename);
+ }
+
+ return array_pop($module);
+ }
+}
diff --git a/phpBB/includes/db/migration/tool/permission.php b/phpBB/includes/db/migration/tool/permission.php
new file mode 100644
index 0000000000..4231fbe1dd
--- /dev/null
+++ b/phpBB/includes/db/migration/tool/permission.php
@@ -0,0 +1,622 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* Migration permission management tool
+*
+* @package db
+*/
+class phpbb_db_migration_tool_permission implements phpbb_db_migration_tool_interface
+{
+ /** @var phpbb_auth */
+ protected $auth;
+
+ /** @var phpbb_cache_service */
+ protected $cache;
+
+ /** @var dbal */
+ protected $db;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /**
+ * Constructor
+ *
+ * @param phpbb_db_driver $db
+ * @param mixed $cache
+ * @param phpbb_auth $auth
+ * @param string $phpbb_root_path
+ * @param string $php_ext
+ */
+ public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_auth $auth, $phpbb_root_path, $php_ext)
+ {
+ $this->db = $db;
+ $this->cache = $cache;
+ $this->auth = $auth;
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get_name()
+ {
+ return 'permission';
+ }
+
+ /**
+ * Permission Exists
+ *
+ * Check if a permission (auth) setting exists
+ *
+ * @param string $auth_option The name of the permission (auth) option
+ * @param bool $global True for checking a global permission setting,
+ * False for a local permission setting
+ * @return bool true if it exists, false if not
+ */
+ public function exists($auth_option, $global = true)
+ {
+ if ($global)
+ {
+ $type_sql = ' AND is_global = 1';
+ }
+ else
+ {
+ $type_sql = ' AND is_local = 1';
+ }
+
+ $sql = 'SELECT auth_option_id
+ FROM ' . ACL_OPTIONS_TABLE . "
+ WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'"
+ . $type_sql;
+ $result = $this->db->sql_query($sql);
+
+ $row = $this->db->sql_fetchrow($result);
+ $this->db->sql_freeresult($result);
+
+ if ($row)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Permission Add
+ *
+ * Add a permission (auth) option
+ *
+ * @param string $auth_option The name of the permission (auth) option
+ * @param bool $global True for checking a global permission setting,
+ * False for a local permission setting
+ * @return null
+ */
+ public function add($auth_option, $global = true, $copy_from = false)
+ {
+ if ($this->exists($auth_option, $global))
+ {
+ throw new phpbb_db_migration_exception('PERMISSION_ALREADY_EXIST', $auth_option);
+ }
+
+ // We've added permissions, so set to true to notify the user.
+ $this->permissions_added = true;
+
+ if (!class_exists('auth_admin'))
+ {
+ include($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext);
+ }
+ $auth_admin = new auth_admin();
+
+ // We have to add a check to see if the !$global (if global, local, and if local, global) permission already exists. If it does, acl_add_option currently has a bug which would break the ACL system, so we are having a work-around here.
+ if ($this->exists($auth_option, !$global))
+ {
+ $sql_ary = array(
+ 'is_global' => 1,
+ 'is_local' => 1,
+ );
+ $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . '
+ SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . "
+ WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'";
+ $this->db->sql_query($sql);
+ }
+ else
+ {
+ if ($global)
+ {
+ $auth_admin->acl_add_option(array('global' => array($auth_option)));
+ }
+ else
+ {
+ $auth_admin->acl_add_option(array('local' => array($auth_option)));
+ }
+ }
+
+ // The permission has been added, now we can copy it if needed
+ if ($copy_from && isset($auth_admin->acl_options['id'][$copy_from]))
+ {
+ $old_id = $auth_admin->acl_options['id'][$copy_from];
+ $new_id = $auth_admin->acl_options['id'][$auth_option];
+
+ $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 = $this->db->sql_query($sql);
+
+ $sql_ary = array();
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $row['auth_option_id'] = $new_id;
+ $sql_ary[] = $row;
+ }
+ $this->db->sql_freeresult($result);
+
+ if (!empty($sql_ary))
+ {
+ $this->db->sql_multi_insert($table, $sql_ary);
+ }
+ }
+
+ $auth_admin->acl_clear_prefetch();
+ }
+ }
+
+ /**
+ * Permission Remove
+ *
+ * Remove a permission (auth) option
+ *
+ * @param string $auth_option The name of the permission (auth) option
+ * @param bool $global True for checking a global permission setting,
+ * False for a local permission setting
+ * @return null
+ */
+ public function remove($auth_option, $global = true)
+ {
+ if (!$this->exists($auth_option, $global))
+ {
+ throw new phpbb_db_migration_exception('PERMISSION_NOT_EXIST', $auth_option);
+ }
+
+ if ($global)
+ {
+ $type_sql = ' AND is_global = 1';
+ }
+ else
+ {
+ $type_sql = ' AND is_local = 1';
+ }
+ $sql = 'SELECT auth_option_id, is_global, is_local
+ FROM ' . ACL_OPTIONS_TABLE . "
+ WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" .
+ $type_sql;
+ $result = $this->db->sql_query($sql);
+ $row = $this->db->sql_fetchrow($result);
+ $this->db->sql_freeresult($result);
+
+ $id = (int) $row['auth_option_id'];
+
+ // If it is a local and global permission, do not remove the row! :P
+ if ($row['is_global'] && $row['is_local'])
+ {
+ $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . '
+ SET ' . (($global) ? 'is_global = 0' : 'is_local = 0') . '
+ WHERE auth_option_id = ' . $id;
+ $this->db->sql_query($sql);
+ }
+ else
+ {
+ // Delete time
+ $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE, ACL_OPTIONS_TABLE);
+ foreach ($tables as $table)
+ {
+ $this->db->sql_query('DELETE FROM ' . $table . '
+ WHERE auth_option_id = ' . $id);
+ }
+ }
+
+ // Purge the auth cache
+ $this->cache->destroy('_acl_options');
+ $this->auth->acl_clear_prefetch();
+ }
+
+ /**
+ * Add a new permission role
+ *
+ * @param string $role_name The new role name
+ * @param sting $role_type The type (u_, m_, a_)
+ * @return null
+ */
+ public function role_add($role_name, $role_type, $role_description = '')
+ {
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($role_name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if ($role_id)
+ {
+ return;
+ }
+
+ $sql = 'SELECT MAX(role_order) AS max_role_order
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_type = '" . $this->db->sql_escape($role_type) . "'";
+ $this->db->sql_query($sql);
+ $role_order = (int) $this->db->sql_fetchfield('max_role_order');
+ $role_order = (!$role_order) ? 1 : $role_order + 1;
+
+ $sql_ary = array(
+ 'role_name' => $role_name,
+ 'role_description' => $role_description,
+ 'role_type' => $role_type,
+ 'role_order' => $role_order,
+ );
+
+ $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary);
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Update the name on a permission role
+ *
+ * @param string $old_role_name The old role name
+ * @param string $new_role_name The new role name
+ * @return null
+ */
+ public function role_update($old_role_name, $new_role_name)
+ {
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if (!$role_id)
+ {
+ throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $old_role_name);
+ }
+
+ $sql = 'UPDATE ' . ACL_ROLES_TABLE . "
+ SET role_name = '" . $this->db->sql_escape($new_role_name) . "'
+ WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'";
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Remove a permission role
+ *
+ * @param string $role_name The role name to remove
+ * @return null
+ */
+ public function role_remove($role_name)
+ {
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($role_name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if (!$role_id)
+ {
+ throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $role_name);
+ }
+
+ $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $this->db->sql_query($sql);
+
+ $sql = 'DELETE FROM ' . ACL_ROLES_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $this->db->sql_query($sql);
+
+ $this->auth->acl_clear_prefetch();
+ }
+
+ /**
+ * Permission Set
+ *
+ * Allows you to set permissions for a certain group/role
+ *
+ * @param string $name The name of the role/group
+ * @param string|array $auth_option The auth_option or array of
+ * auth_options you would like to set
+ * @param string $type The type (role|group)
+ * @param bool $has_permission True if you want to give them permission,
+ * false if you want to deny them permission
+ * @return null
+ */
+ public function permission_set($name, $auth_option, $type = 'role', $has_permission = true)
+ {
+ if (!is_array($auth_option))
+ {
+ $auth_option = array($auth_option);
+ }
+
+ $new_auth = array();
+ $sql = 'SELECT auth_option_id
+ FROM ' . ACL_OPTIONS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('auth_option', $auth_option);
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $new_auth[] = (int) $row['auth_option_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (empty($new_auth))
+ {
+ return;
+ }
+
+ $current_auth = array();
+
+ $type = (string) $type; // Prevent PHP bug.
+
+ switch ($type)
+ {
+ case 'role':
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if (!$role_id)
+ {
+ throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name);
+ }
+
+ $sql = 'SELECT auth_option_id, auth_setting
+ FROM ' . ACL_ROLES_DATA_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $current_auth[$row['auth_option_id']] = $row['auth_setting'];
+ }
+ $this->db->sql_freeresult($result);
+ break;
+
+ case 'group':
+ $sql = 'SELECT group_id
+ FROM ' . GROUPS_TABLE . "
+ WHERE group_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ $group_id = (int) $this->db->sql_fetchfield('group_id');
+
+ if (!$group_id)
+ {
+ throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name);
+ }
+
+ // If the group has a role set for them we will add the requested permissions to that role.
+ $sql = 'SELECT auth_role_id
+ FROM ' . ACL_GROUPS_TABLE . '
+ WHERE group_id = ' . $group_id . '
+ AND auth_role_id <> 0
+ AND forum_id = 0';
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('auth_role_id');
+ if ($role_id)
+ {
+ $sql = 'SELECT role_name
+ FROM ' . ACL_ROLES_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $this->db->sql_query($sql);
+ $role_name = $this->db->sql_fetchfield('role_name');
+
+ return $this->set($role_name, $auth_option, 'role', $has_permission);
+ }
+
+ $sql = 'SELECT auth_option_id, auth_setting
+ FROM ' . ACL_GROUPS_TABLE . '
+ WHERE group_id = ' . $group_id;
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $current_auth[$row['auth_option_id']] = $row['auth_setting'];
+ }
+ $this->db->sql_freeresult($result);
+ break;
+ }
+
+ $sql_ary = array();
+ switch ($type)
+ {
+ case 'role':
+ foreach ($new_auth as $auth_option_id)
+ {
+ if (!isset($current_auth[$auth_option_id]))
+ {
+ $sql_ary[] = array(
+ 'role_id' => $role_id,
+ 'auth_option_id' => $auth_option_id,
+ 'auth_setting' => $has_permission,
+ );
+ }
+ }
+
+ $this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary);
+ break;
+
+ case 'group':
+ foreach ($new_auth as $auth_option_id)
+ {
+ if (!isset($current_auth[$auth_option_id]))
+ {
+ $sql_ary[] = array(
+ 'group_id' => $group_id,
+ 'auth_option_id' => $auth_option_id,
+ 'auth_setting' => $has_permission,
+ );
+ }
+ }
+
+ $this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary);
+ break;
+ }
+
+ $this->auth->acl_clear_prefetch();
+ }
+
+ /**
+ * Permission Unset
+ *
+ * Allows you to unset (remove) permissions for a certain group/role
+ *
+ * @param string $name The name of the role/group
+ * @param string|array $auth_option The auth_option or array of
+ * auth_options you would like to set
+ * @param string $type The type (role|group)
+ * @return null
+ */
+ public function permission_unset($name, $auth_option, $type = 'role')
+ {
+ if (!is_array($auth_option))
+ {
+ $auth_option = array($auth_option);
+ }
+
+ $to_remove = array();
+ $sql = 'SELECT auth_option_id
+ FROM ' . ACL_OPTIONS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('auth_option', $auth_option);
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $to_remove[] = (int) $row['auth_option_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (empty($to_remove))
+ {
+ return;
+ }
+
+ $type = (string) $type; // Prevent PHP bug.
+
+ switch ($type)
+ {
+ case 'role':
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if (!$role_id)
+ {
+ throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name);
+ }
+
+ $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . '
+ WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove);
+ $this->db->sql_query($sql);
+ break;
+
+ case 'group':
+ $sql = 'SELECT group_id
+ FROM ' . GROUPS_TABLE . "
+ WHERE group_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ $group_id = (int) $this->db->sql_fetchfield('group_id');
+
+ if (!$group_id)
+ {
+ throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name);
+ }
+
+ // If the group has a role set for them we will remove the requested permissions from that role.
+ $sql = 'SELECT auth_role_id
+ FROM ' . ACL_GROUPS_TABLE . '
+ WHERE group_id = ' . $group_id . '
+ AND auth_role_id <> 0';
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('auth_role_id');
+ if ($role_id)
+ {
+ $sql = 'SELECT role_name
+ FROM ' . ACL_ROLES_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $this->db->sql_query($sql);
+ $role_name = $this->db->sql_fetchfield('role_name');
+
+ return $this->permission_unset($role_name, $auth_option, 'role');
+ }
+
+ $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove);
+ $this->db->sql_query($sql);
+ break;
+ }
+
+ $this->auth->acl_clear_prefetch();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reverse()
+ {
+ $arguments = func_get_args();
+ $original_call = array_shift($arguments);
+
+ $call = false;
+ switch ($original_call)
+ {
+ case 'add':
+ $call = 'remove';
+ break;
+
+ case 'remove':
+ $call = 'add';
+ break;
+
+ case 'permission_set':
+ $call = 'permission_unset';
+ break;
+
+ case 'permission_unset':
+ $call = 'permission_set';
+ break;
+
+ case 'role_add':
+ $call = 'role_remove';
+ break;
+
+ case 'role_remove':
+ $call = 'role_add';
+ break;
+
+ case 'role_update':
+ // Set to the original value if the current value is what we compared to originally
+ $arguments = array(
+ $arguments[1],
+ $arguments[0],
+ );
+ break;
+ }
+
+ if ($call)
+ {
+ return call_user_func_array(array(&$this, $call), $arguments);
+ }
+ }
+}
diff --git a/phpBB/includes/db/migrator.php b/phpBB/includes/db/migrator.php
new file mode 100644
index 0000000000..de9c06948c
--- /dev/null
+++ b/phpBB/includes/db/migrator.php
@@ -0,0 +1,757 @@
+<?php
+/**
+*
+* @package db
+* @copyright (c) 2011 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* The migrator is responsible for applying new migrations in the correct order.
+*
+* @package db
+*/
+class phpbb_db_migrator
+{
+ /** @var phpbb_config */
+ protected $config;
+
+ /** @var phpbb_db_driver */
+ protected $db;
+
+ /** @var phpbb_db_tools */
+ protected $db_tools;
+
+ /** @var phpbb_extension_manager */
+ protected $extension_manager;
+
+ /** @var string */
+ protected $table_prefix;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /** @var string */
+ protected $migrations_table;
+
+ /**
+ * State of all migrations
+ *
+ * (SELECT * FROM migrations table)
+ *
+ * @var array
+ */
+ protected $migration_state = array();
+
+ /**
+ * Array of all migrations available to be run
+ *
+ * @var array
+ */
+ protected $migrations = array();
+
+ /**
+ * 'name' and 'class' of the last migration run
+ *
+ * @var array
+ */
+ public $last_run_migration = false;
+
+ /**
+ * Constructor of the database migrator
+ */
+ public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools)
+ {
+ $this->config = $config;
+ $this->db = $db;
+ $this->db_tools = $db_tools;
+
+ $this->migrations_table = $migrations_table;
+
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+
+ $this->table_prefix = $table_prefix;
+
+ foreach ($tools as $tool)
+ {
+ $this->tools[$tool->get_name()] = $tool;
+ }
+
+ $this->load_migration_state();
+ }
+
+ /**
+ * Set Extension Manager (required)
+ *
+ * Not in constructor to prevent circular reference error
+ */
+ public function set_extension_manager(phpbb_extension_manager $extension_manager)
+ {
+ $this->extension_manager = $extension_manager;
+ }
+
+ /**
+ * Loads all migrations and their application state from the database.
+ *
+ * @return null
+ */
+ public function load_migration_state()
+ {
+ $this->migration_state = array();
+
+ // prevent errors in case the table does not exist yet
+ $this->db->sql_return_on_error(true);
+
+ $sql = "SELECT *
+ FROM " . $this->migrations_table;
+ $result = $this->db->sql_query($sql);
+
+ if (!$this->db->sql_error_triggered)
+ {
+ while ($migration = $this->db->sql_fetchrow($result))
+ {
+ $this->migration_state[$migration['migration_name']] = $migration;
+
+ $this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']);
+ }
+ }
+
+ $this->db->sql_freeresult($result);
+
+ $this->db->sql_return_on_error(false);
+ }
+
+ /**
+ * Sets the list of available migration class names to the given array.
+ *
+ * @param array $class_names An array of migration class names
+ * @return null
+ */
+ public function set_migrations($class_names)
+ {
+ $this->migrations = $class_names;
+ }
+
+ /**
+ * This function adds all migrations in a specified directory to the migrations table
+ *
+ * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER.
+ * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE!
+ *
+ * @param string $path Path to migration data files
+ * @param bool $recursive Set to true to also load data files from subdirectories
+ * @return null
+ */
+ public function populate_migrations_from_directory($path, $recursive = true)
+ {
+ $existing_migrations = $this->migrations;
+
+ $this->migrations = array();
+ $this->load_migrations($path, true, $recursive);
+
+ foreach ($this->migrations as $name)
+ {
+ if ($this->migration_state($name) === false)
+ {
+ $state = array(
+ 'migration_depends_on' => $name::depends_on(),
+ 'migration_schema_done' => true,
+ 'migration_data_done' => true,
+ 'migration_data_state' => '',
+ 'migration_start_time' => time(),
+ 'migration_end_time' => time(),
+ );
+ $this->insert_migration($name, $state);
+ }
+ }
+
+ $this->migrations = $existing_migrations;
+ }
+
+ /**
+ * Load migration data files from a directory
+ *
+ * Migration data files loaded with this function MUST contain
+ * ONLY ONE class in them (or an exception will be thrown).
+ *
+ * @param string $path Path to migration data files
+ * @param bool $check_fulfillable If TRUE (default), we will check
+ * if all of the migrations are fulfillable after loading them.
+ * If FALSE, we will not check. You SHOULD check at least once
+ * to prevent errors (if including multiple directories, check
+ * with the last call to prevent throwing errors unnecessarily).
+ * @return array Array of migration names
+ */
+ public function load_migrations($path, $check_fulfillable = true)
+ {
+ if (!is_dir($path))
+ {
+ throw new phpbb_db_migration_exception('DIRECTORY INVALID', $path);
+ }
+
+ $migrations = array();
+
+ $finder = $this->extension_manager->get_finder();
+ $files = $finder
+ ->extension_directory("/")
+ ->find_from_paths(array('/' => $path));
+ foreach ($files as $file)
+ {
+ $migrations[$file['path'] . $file['filename']] = '';
+ }
+ $migrations = $finder->get_classes_from_files($migrations);
+
+ foreach ($migrations as $migration)
+ {
+ if (!in_array($migration, $this->migrations))
+ {
+ $this->migrations[] = $migration;
+ }
+ }
+
+ if ($check_fulfillable)
+ {
+ foreach ($this->migrations as $name)
+ {
+ $unfulfillable = $this->unfulfillable($name);
+ if ($unfulfillable !== false)
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_NOT_FULFILLABLE', $name, $unfulfillable);
+ }
+ }
+ }
+
+ return $this->migrations;
+ }
+
+ /**
+ * Runs a single update step from the next migration to be applied.
+ *
+ * The update step can either be a schema or a (partial) data update. To
+ * check if update() needs to be called again use the finished() method.
+ *
+ * @return null
+ */
+ public function update()
+ {
+ foreach ($this->migrations as $name)
+ {
+ if (!isset($this->migration_state[$name]) ||
+ !$this->migration_state[$name]['migration_schema_done'] ||
+ !$this->migration_state[$name]['migration_data_done'])
+ {
+ if (!$this->try_apply($name))
+ {
+ continue;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Attempts to apply a step of the given migration or one of its dependencies
+ *
+ * @param string The class name of the migration
+ * @return bool Whether any update step was successfully run
+ */
+ protected function try_apply($name)
+ {
+ if (!class_exists($name))
+ {
+ return false;
+ }
+
+ $migration = $this->get_migration($name);
+
+ $state = (isset($this->migration_state[$name])) ?
+ $this->migration_state[$name] :
+ array(
+ 'migration_depends_on' => $migration->depends_on(),
+ 'migration_schema_done' => false,
+ 'migration_data_done' => false,
+ 'migration_data_state' => '',
+ 'migration_start_time' => 0,
+ 'migration_end_time' => 0,
+ );
+
+ foreach ($state['migration_depends_on'] as $depend)
+ {
+ if (!isset($this->migration_state[$depend]) ||
+ !$this->migration_state[$depend]['migration_schema_done'] ||
+ !$this->migration_state[$depend]['migration_data_done'])
+ {
+ return $this->try_apply($depend);
+ }
+ }
+
+ $this->last_run_migration = array(
+ 'name' => $name,
+ 'class' => $migration,
+ );
+
+ if (!isset($this->migration_state[$name]))
+ {
+ if ($migration->effectively_installed())
+ {
+ $state = array(
+ 'migration_depends_on' => $migration->depends_on(),
+ 'migration_schema_done' => true,
+ 'migration_data_done' => true,
+ 'migration_data_state' => '',
+ 'migration_start_time' => 0,
+ 'migration_end_time' => 0,
+ );
+ }
+ else
+ {
+ $state['migration_start_time'] = time();
+ }
+ }
+
+ if (!$state['migration_schema_done'])
+ {
+ $this->apply_schema_changes($migration->update_schema());
+ $state['migration_schema_done'] = true;
+ }
+ else if (!$state['migration_data_done'])
+ {
+ try
+ {
+ $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']);
+
+ $state['migration_data_state'] = ($result === true) ? '' : $result;
+ $state['migration_data_done'] = ($result === true);
+ $state['migration_end_time'] = ($result === true) ? time() : 0;
+ }
+ catch (phpbb_db_migration_exception $e)
+ {
+ // Revert the schema changes
+ $this->revert($name);
+
+ // Rethrow exception
+ throw $e;
+ }
+ }
+
+ $this->insert_migration($name, $state);
+
+ return true;
+ }
+
+ /**
+ * Runs a single revert step from the last migration installed
+ *
+ * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD!
+ * The revert step can either be a schema or a (partial) data revert. To
+ * check if revert() needs to be called again use the migration_state() method.
+ *
+ * @param string $migration String migration name to revert (including any that depend on this migration)
+ * @return null
+ */
+ public function revert($migration)
+ {
+ if (!isset($this->migration_state[$migration]))
+ {
+ // Not installed
+ return;
+ }
+
+ foreach ($this->migration_state as $name => $state)
+ {
+ if (!empty($state['migration_depends_on']) && in_array($migration, $state['migration_depends_on']))
+ {
+ $this->revert($name);
+ }
+ }
+
+ $this->try_revert($migration);
+ }
+
+ /**
+ * Attempts to revert a step of the given migration or one of its dependencies
+ *
+ * @param string The class name of the migration
+ * @return bool Whether any update step was successfully run
+ */
+ protected function try_revert($name)
+ {
+ if (!class_exists($name))
+ {
+ return false;
+ }
+
+ $migration = $this->get_migration($name);
+
+ $state = $this->migration_state[$name];
+
+ $this->last_run_migration = array(
+ 'name' => $name,
+ 'class' => $migration,
+ );
+
+ if ($state['migration_data_done'])
+ {
+ if ($state['migration_data_state'] !== 'revert_data')
+ {
+ $result = $this->process_data_step($migration->update_data(), $state['migration_data_state'], true);
+
+ $state['migration_data_state'] = ($result === true) ? 'revert_data' : $result;
+ }
+ else
+ {
+ $result = $this->process_data_step($migration->revert_data(), '', false);
+
+ $state['migration_data_state'] = ($result === true) ? '' : $result;
+ $state['migration_data_done'] = ($result === true) ? false : true;
+ }
+
+ $this->insert_migration($name, $state);
+ }
+ else
+ {
+ $this->apply_schema_changes($migration->revert_schema());
+
+ $sql = 'DELETE FROM ' . $this->migrations_table . "
+ WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+
+ unset($this->migration_state[$name]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Apply schema changes from a migration
+ *
+ * Just calls db_tools->perform_schema_changes
+ *
+ * @param array $schema_changes from migration
+ */
+ protected function apply_schema_changes($schema_changes)
+ {
+ $this->db_tools->perform_schema_changes($schema_changes);
+ }
+
+ /**
+ * Process the data step of the migration
+ *
+ * @param array $steps The steps to run
+ * @param bool|string $state Current state of the migration
+ * @param bool $revert true to revert a data step
+ * @return bool|string migration state. True if completed, serialized array if not finished
+ */
+ protected function process_data_step($steps, $state, $revert = false)
+ {
+ $state = ($state) ? unserialize($state) : false;
+
+ foreach ($steps as $step_identifier => $step)
+ {
+ $last_result = false;
+ if ($state)
+ {
+ // Continue until we reach the step that matches the last step called
+ if ($state['step'] != $step_identifier)
+ {
+ continue;
+ }
+
+ // We send the result from last time to the callable function
+ $last_result = $state['result'];
+
+ // Set state to false since we reached the point we were at
+ $state = false;
+ }
+
+ try
+ {
+ // Result will be null or true if everything completed correctly
+ $result = $this->run_step($step, $last_result, $revert);
+ if ($result !== null && $result !== true)
+ {
+ return serialize(array(
+ 'result' => $result,
+ 'step' => $step_identifier,
+ ));
+ }
+ }
+ catch (phpbb_db_migration_exception $e)
+ {
+ // We should try rolling back here
+ foreach ($steps as $reverse_step_identifier => $reverse_step)
+ {
+ // If we've reached the current step we can break because we reversed everything that was run
+ if ($reverse_step_identifier == $step_identifier)
+ {
+ break;
+ }
+
+ // Reverse the step that was run
+ $result = $this->run_step($reverse_step, false, !$revert);
+ }
+
+ // rethrow the exception
+ throw $e;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Run a single step
+ *
+ * An exception should be thrown if an error occurs
+ *
+ * @param mixed $step Data step from migration
+ * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
+ * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
+ * @return null
+ */
+ protected function run_step($step, $last_result = false, $reverse = false)
+ {
+ $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse);
+
+ if ($callable_and_parameters === false)
+ {
+ return;
+ }
+
+ $callable = $callable_and_parameters[0];
+ $parameters = $callable_and_parameters[1];
+
+ return call_user_func_array($callable, $parameters);
+ }
+
+ /**
+ * Get a callable statement from a data step
+ *
+ * @param array $step Data step from migration
+ * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
+ * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
+ * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters
+ */
+ protected function get_callable_from_step(array $step, $last_result = false, $reverse = false)
+ {
+ $type = $step[0];
+ $parameters = $step[1];
+
+ $parts = explode('.', $type);
+
+ $class = $parts[0];
+ $method = false;
+
+ if (isset($parts[1]))
+ {
+ $method = $parts[1];
+ }
+
+ switch ($class)
+ {
+ case 'if':
+ if (!isset($parameters[0]))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step);
+ }
+
+ if (!isset($parameters[1]))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step);
+ }
+
+ $condition = $parameters[0];
+
+ if (!$condition)
+ {
+ return false;
+ }
+
+ $step = $parameters[1];
+
+ return $this->get_callable_from_step($step);
+ break;
+ case 'custom':
+ if (!is_callable($parameters[0]))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step);
+ }
+
+ return array(
+ $parameters[0],
+ array($last_result),
+ );
+ break;
+
+ default:
+ if (!$method)
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step);
+ }
+
+ if (!isset($this->tools[$class]))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step);
+ }
+
+ if (!method_exists(get_class($this->tools[$class]), $method))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step);
+ }
+
+ // Attempt to reverse operations
+ if ($reverse)
+ {
+ array_unshift($parameters, $method);
+
+ return array(
+ array($this->tools[$class], 'reverse'),
+ $parameters,
+ );
+ }
+
+ return array(
+ array($this->tools[$class], $method),
+ $parameters,
+ );
+ break;
+ }
+ }
+
+ /**
+ * Insert/Update migration row into the database
+ *
+ * @param string $name Name of the migration
+ * @param array $state
+ * @return null
+ */
+ protected function insert_migration($name, $state)
+ {
+ $migration_row = $state;
+ $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']);
+
+ if (isset($this->migration_state[$name]))
+ {
+ $sql = 'UPDATE ' . $this->migrations_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . "
+ WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ }
+ else
+ {
+ $migration_row['migration_name'] = $name;
+ $sql = 'INSERT INTO ' . $this->migrations_table . '
+ ' . $this->db->sql_build_array('INSERT', $migration_row);
+ $this->db->sql_query($sql);
+ }
+
+ $this->migration_state[$name] = $state;
+ }
+
+ /**
+ * Checks if a migration's dependencies can even theoretically be satisfied.
+ *
+ * @param string $name The class name of the migration
+ * @return bool|string False if fulfillable, string of missing migration name if unfulfillable
+ */
+ public function unfulfillable($name)
+ {
+ if (isset($this->migration_state[$name]))
+ {
+ return false;
+ }
+
+ if (!class_exists($name))
+ {
+ return $name;
+ }
+
+ $migration = $this->get_migration($name);
+ $depends = $migration->depends_on();
+
+ foreach ($depends as $depend)
+ {
+ $unfulfillable = $this->unfulfillable($depend);
+ if ($unfulfillable !== false)
+ {
+ return $unfulfillable;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether all available, fulfillable migrations have been applied.
+ *
+ * @return bool Whether the migrations have been applied
+ */
+ public function finished()
+ {
+ foreach ($this->migrations as $name)
+ {
+ if (!isset($this->migration_state[$name]))
+ {
+ // skip unfulfillable migrations, but fulfillables mean we
+ // are not finished yet
+ if ($this->unfulfillable($name) !== false)
+ {
+ continue;
+ }
+ return false;
+ }
+
+ $migration = $this->migration_state[$name];
+
+ if (!$migration['migration_schema_done'] || !$migration['migration_data_done'])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets a migration state (whether it is installed and to what extent)
+ *
+ * @param string $migration String migration name to check if it is installed
+ * @return bool|array False if the migration has not at all been installed, array
+ */
+ public function migration_state($migration)
+ {
+ if (!isset($this->migration_state[$migration]))
+ {
+ return false;
+ }
+
+ return $this->migration_state[$migration];
+ }
+
+ /**
+ * Helper to get a migration
+ *
+ * @param string $name Name of the migration
+ * @return phpbb_db_migration
+ */
+ protected function get_migration($name)
+ {
+ return new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix);
+ }
+}
diff --git a/phpBB/includes/di/extension/config.php b/phpBB/includes/di/extension/config.php
index 97a6290066..6c272a6588 100644
--- a/phpBB/includes/di/extension/config.php
+++ b/phpBB/includes/di/extension/config.php
@@ -42,6 +42,7 @@ class phpbb_di_extension_config extends Extension
{
require($this->config_file);
+ $container->setParameter('core.adm_relative_path', (isset($phpbb_adm_relative_path) ? $phpbb_adm_relative_path : 'adm/'));
$container->setParameter('core.table_prefix', $table_prefix);
$container->setParameter('cache.driver.class', $this->convert_30_acm_type($acm_type));
$container->setParameter('dbal.driver.class', phpbb_convert_30_dbms_to_31($dbms));
diff --git a/phpBB/includes/extension/base.php b/phpBB/includes/extension/base.php
index 9d076eb6c5..d51589d719 100644
--- a/phpBB/includes/extension/base.php
+++ b/phpBB/includes/extension/base.php
@@ -15,6 +15,8 @@ if (!defined('IN_PHPBB'))
exit;
}
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
/**
* A base class for extensions without custom enable/disable/purge code.
*
@@ -22,6 +24,19 @@ if (!defined('IN_PHPBB'))
*/
class phpbb_extension_base implements phpbb_extension_interface
{
+ /** @var ContainerInterface */
+ protected $container;
+
+ /**
+ * Constructor
+ *
+ * @param ContainerInterface $container Container object
+ */
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
/**
* Single enable step that does nothing
*
diff --git a/phpBB/includes/extension/finder.php b/phpBB/includes/extension/finder.php
index fb19b98429..f71e32bc8d 100644
--- a/phpBB/includes/extension/finder.php
+++ b/phpBB/includes/extension/finder.php
@@ -247,15 +247,28 @@ class phpbb_extension_finder
* phpBB naming rules an incorrect class name will be returned.
*
* @param bool $cache Whether the result should be cached
+ * @param bool $use_all_available Use all available instead of just all
+ * enabled extensions
* @return array An array of found class names
*/
- public function get_classes($cache = true)
+ public function get_classes($cache = true, $use_all_available = false)
{
$this->query['extension_suffix'] .= $this->php_ext;
$this->query['core_suffix'] .= $this->php_ext;
- $files = $this->find($cache, false);
+ $files = $this->find($cache, false, $use_all_available);
+ return $this->get_classes_from_files($files);
+ }
+
+ /**
+ * Get class names from a list of files
+ *
+ * @param array $files Array of files (from find())
+ * @return array Array of class names
+ */
+ public function get_classes_from_files($files)
+ {
$classes = array();
foreach ($files as $file => $ext_name)
{
@@ -270,23 +283,27 @@ class phpbb_extension_finder
* Finds all directories matching the configured options
*
* @param bool $cache Whether the result should be cached
+ * @param bool $use_all_available Use all available instead of just all
+ * enabled extensions
* @param bool $extension_keys Whether the result should have extension name as array key
* @return array An array of paths to found directories
*/
- public function get_directories($cache = true, $extension_keys = false)
+ public function get_directories($cache = true, $use_all_available = false, $extension_keys = false)
{
- return $this->find_with_root_path($cache, true, $extension_keys);
+ return $this->find_with_root_path($cache, true, $use_all_available, $extension_keys);
}
/**
* Finds all files matching the configured options.
*
* @param bool $cache Whether the result should be cached
+ * @param bool $use_all_available Use all available instead of just all
+ * enabled extensions
* @return array An array of paths to found files
*/
- public function get_files($cache = true)
+ public function get_files($cache = true, $use_all_available = false)
{
- return $this->find_with_root_path($cache, false);
+ return $this->find_with_root_path($cache, false, $use_all_available);
}
/**
@@ -295,13 +312,15 @@ class phpbb_extension_finder
* @param bool $cache Whether the result should be cached
* @param bool $is_dir Directories will be returned when true, only files
* otherwise
+ * @param bool $use_all_available Use all available instead of just all
+ * enabled extensions
* @param bool $extension_keys If true, result will be associative array
* with extension name as key
* @return array An array of paths to found items
*/
- protected function find_with_root_path($cache = true, $is_dir = false, $extension_keys = false)
+ protected function find_with_root_path($cache = true, $is_dir = false, $use_all_available = false, $extension_keys = false)
{
- $items = $this->find($cache, $is_dir);
+ $items = $this->find($cache, $is_dir, $use_all_available);
$result = array();
foreach ($items as $item => $ext_name)
@@ -325,27 +344,59 @@ class phpbb_extension_finder
* @param bool $cache Whether the result should be cached
* @param bool $is_dir Directories will be returned when true, only files
* otherwise
+ * @param bool $use_all_available Use all available instead of just all
+ * enabled extensions
* @return array An array of paths to found items
*/
- public function find($cache = true, $is_dir = false)
+ public function find($cache = true, $is_dir = false, $use_all_available = false)
{
- $this->query['is_dir'] = $is_dir;
- $query = md5(serialize($this->query));
+ if ($use_all_available)
+ {
+ $extensions = $this->extension_manager->all_available();
+ }
+ else
+ {
+ $extensions = $this->extension_manager->all_enabled();
+ }
- if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query]))
+ if ($this->query['core_path'])
{
- return $this->cached_queries[$query];
+ $extensions['/'] = $this->phpbb_root_path . $this->query['core_path'];
}
$files = array();
+ $file_list = $this->find_from_paths($extensions, $cache, $is_dir);
- $extensions = $this->extension_manager->all_enabled();
+ foreach ($file_list as $file)
+ {
+ $files[$file['named_path']] = $file['ext_name'];
+ }
- if ($this->query['core_path'])
+ return $files;
+ }
+
+ /**
+ * Finds all file system entries matching the configured options from
+ * an array of paths
+ *
+ * @param array $extensions Array of extensions (name => full relative path)
+ * @param bool $cache Whether the result should be cached
+ * @param bool $is_dir Directories will be returned when true, only files
+ * otherwise
+ * @return array An array of paths to found items
+ */
+ public function find_from_paths($extensions, $cache = true, $is_dir = false)
+ {
+ $this->query['is_dir'] = $is_dir;
+ $query = md5(serialize($this->query) . serialize($extensions));
+
+ if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query]))
{
- $extensions['/'] = $this->phpbb_root_path . $this->query['core_path'];
+ return $this->cached_queries[$query];
}
+ $files = array();
+
foreach ($extensions as $name => $path)
{
$ext_name = $name;
@@ -419,7 +470,12 @@ class phpbb_extension_finder
(!$prefix || substr($filename, 0, strlen($prefix)) === $prefix) &&
(!$directory || preg_match($directory_pattern, $relative_path)))
{
- $files[str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1))] = $ext_name;
+ $files[] = array(
+ 'named_path' => str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1)),
+ 'ext_name' => $ext_name,
+ 'path' => str_replace(array(DIRECTORY_SEPARATOR, $this->phpbb_root_path), array('/', ''), $file_info->getPath()) . '/',
+ 'filename' => $filename,
+ );
}
}
}
diff --git a/phpBB/includes/extension/interface.php b/phpBB/includes/extension/interface.php
index 74ecb9b762..7b36a12bf6 100644
--- a/phpBB/includes/extension/interface.php
+++ b/phpBB/includes/extension/interface.php
@@ -45,7 +45,9 @@ interface phpbb_extension_interface
*
* @param mixed $old_state The return value of the previous call
* of this method, or false on the first call
- * @return null
+ * @return mixed Returns false after last step, otherwise
+ * temporary state which is passed as an
+ * argument to the next step
*/
public function disable_step($old_state);
diff --git a/phpBB/includes/extension/manager.php b/phpBB/includes/extension/manager.php
index 67cc81e407..0d760681b9 100644
--- a/phpBB/includes/extension/manager.php
+++ b/phpBB/includes/extension/manager.php
@@ -15,6 +15,8 @@ if (!defined('IN_PHPBB'))
exit;
}
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
/**
* The extension manager provides means to activate/deactivate extensions.
*
@@ -22,8 +24,12 @@ if (!defined('IN_PHPBB'))
*/
class phpbb_extension_manager
{
+ /** @var ContainerInterface */
+ protected $container;
+
protected $db;
protected $config;
+ protected $migrator;
protected $cache;
protected $php_ext;
protected $extensions;
@@ -34,6 +40,7 @@ class phpbb_extension_manager
/**
* Creates a manager and loads information from database
*
+ * @param ContainerInterface $container A container
* @param phpbb_db_driver $db A database connection
* @param phpbb_config $config phpbb_config
* @param string $extension_table The name of the table holding extensions
@@ -42,8 +49,9 @@ class phpbb_extension_manager
* @param phpbb_cache_driver_interface $cache A cache instance or null
* @param string $cache_name The name of the cache variable, defaults to _ext
*/
- public function __construct(phpbb_db_driver $db, phpbb_config $config, $extension_table, $phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext')
+ public function __construct(ContainerInterface $container, phpbb_db_driver $db, phpbb_config $config, $extension_table, $phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext')
{
+ $this->container = $container;
$this->phpbb_root_path = $phpbb_root_path;
$this->db = $db;
$this->config = $config;
@@ -61,6 +69,14 @@ class phpbb_extension_manager
}
/**
+ * Set migrator (get around circular reference)
+ */
+ public function set_migrator(phpbb_db_migrator $migrator)
+ {
+ $this->migrator = $migrator;
+ }
+
+ /**
* Loads all extension information from the database
*
* @return null
@@ -126,11 +142,11 @@ class phpbb_extension_manager
if (class_exists($extension_class_name))
{
- return new $extension_class_name;
+ return new $extension_class_name($this->container);
}
else
{
- return new phpbb_extension_base;
+ return new phpbb_extension_base($this->container);
}
}
@@ -166,6 +182,12 @@ class phpbb_extension_manager
$old_state = (isset($this->extensions[$name]['ext_state'])) ? unserialize($this->extensions[$name]['ext_state']) : false;
+ // Returns false if not completed
+ if (!$this->handle_migrations($name, 'enable'))
+ {
+ return true;
+ }
+
$extension = $this->get_extension($name);
$state = $extension->enable_step($old_state);
@@ -317,6 +339,12 @@ class phpbb_extension_manager
$old_state = unserialize($this->extensions[$name]['ext_state']);
+ // Returns false if not completed
+ if (!$this->handle_migrations($name, 'purge'))
+ {
+ return true;
+ }
+
$extension = $this->get_extension($name);
$state = $extension->purge_step($old_state);
@@ -384,7 +412,7 @@ class phpbb_extension_manager
}
$iterator = new RecursiveIteratorIterator(
- new RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/'),
+ new RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/', FilesystemIterator::NEW_CURRENT_AND_KEY | FilesystemIterator::FOLLOW_SYMLINKS),
RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file_info)
{
@@ -490,4 +518,58 @@ class phpbb_extension_manager
{
return new phpbb_extension_finder($this, $this->phpbb_root_path, $this->cache, $this->php_ext, $this->cache_name . '_finder');
}
+
+ /**
+ * Handle installing/reverting migrations
+ *
+ * @param string $extension_name Name of the extension
+ * @param string $mode enable or purge
+ * @return bool True if completed, False if not completed
+ */
+ protected function handle_migrations($extension_name, $mode)
+ {
+ $migrations_path = $this->phpbb_root_path . $this->get_extension_path($extension_name) . 'migrations/';
+ if (!file_exists($migrations_path) || !is_dir($migrations_path))
+ {
+ return true;
+ }
+
+ $migrations = $this->migrator->load_migrations($migrations_path);
+
+ // What is a safe limit of execution time? Half the max execution time should be safe.
+ $safe_time_limit = (ini_get('max_execution_time') / 2);
+ $start_time = time();
+
+ if ($mode == 'enable')
+ {
+ while (!$this->migrator->finished())
+ {
+ $this->migrator->update();
+
+ // Are we approaching the time limit? If so we want to pause the update and continue after refreshing
+ if ((time() - $start_time) >= $safe_time_limit)
+ {
+ return false;
+ }
+ }
+ }
+ else if ($mode == 'purge')
+ {
+ foreach ($migrations as $migration)
+ {
+ while ($this->migrator->migration_state($migration) !== false)
+ {
+ $this->migrator->revert($migration);
+
+ // Are we approaching the time limit? If so we want to pause the update and continue after refreshing
+ if ((time() - $start_time) >= $safe_time_limit)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
}
diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php
index d8fd2ff6e6..5701b8baca 100644
--- a/phpBB/includes/functions.php
+++ b/phpBB/includes/functions.php
@@ -97,7 +97,18 @@ function request_var($var_name, $default, $multibyte = false, $cookie = false, $
}
/**
-* Set config value. Creates missing config entry.
+* Sets a configuration option's value.
+*
+* Please note that this function does not update the is_dynamic value for
+* an already existing config option.
+*
+* @param string $config_name The configuration option's name
+* @param string $config_value New configuration value
+* @param bool $is_dynamic Whether this variable should be cached (false) or
+* if it changes too frequently (true) to be
+* efficiently cached.
+*
+* @return null
*
* @deprecated
*/
@@ -119,7 +130,15 @@ function set_config($config_name, $config_value, $is_dynamic = false, phpbb_conf
}
/**
-* Set dynamic config value with arithmetic operation.
+* Increments an integer config value directly in the database.
+*
+* @param string $config_name The configuration option's name
+* @param int $increment Amount to increment by
+* @param bool $is_dynamic Whether this variable should be cached (false) or
+* if it changes too frequently (true) to be
+* efficiently cached.
+*
+* @return null
*
* @deprecated
*/
@@ -1328,7 +1347,7 @@ function phpbb_timezone_select($user, $default = '', $truncate = false)
function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
{
global $db, $user, $config;
- global $request;
+ global $request, $phpbb_container;
$post_time = ($post_time === 0 || $post_time > time()) ? time() : (int) $post_time;
@@ -1336,6 +1355,20 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $
{
if ($forum_id === false || !sizeof($forum_id))
{
+ // Mark all forums read (index page)
+
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ // Mark all topic notifications read for this user
+ $phpbb_notifications->mark_notifications_read(array(
+ 'topic',
+ 'quote',
+ 'bookmark',
+ 'post',
+ 'approve_topic',
+ 'approve_post',
+ ), false, $user->data['user_id'], $post_time);
+
if ($config['load_db_lastread'] && $user->data['is_registered'])
{
// Mark all forums read (index page)
@@ -1390,6 +1423,32 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $
$forum_id = array($forum_id);
}
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ $phpbb_notifications->mark_notifications_read_by_parent(array(
+ 'topic',
+ 'approve_topic',
+ ), $forum_id, $user->data['user_id'], $post_time);
+
+ // Mark all post/quote notifications read for this user in this forum
+ $topic_ids = array();
+ $sql = 'SELECT topic_id
+ FROM ' . TOPICS_TABLE . '
+ WHERE ' . $db->sql_in_set('forum_id', $forum_id);
+ $result = $db->sql_query($sql);
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $topic_ids[] = $row['topic_id'];
+ }
+ $db->sql_freeresult($result);
+
+ $phpbb_notifications->mark_notifications_read_by_parent(array(
+ 'quote',
+ 'bookmark',
+ 'post',
+ 'approve_post',
+ ), $topic_ids, $user->data['user_id'], $post_time);
+
// Add 0 to forums array to mark global announcements correctly
// $forum_id[] = 0;
@@ -1487,6 +1546,21 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $
return;
}
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ // Mark post notifications read for this user in this topic
+ $phpbb_notifications->mark_notifications_read(array(
+ 'topic',
+ 'approve_topic',
+ ), $topic_id, $user->data['user_id'], $post_time);
+
+ $phpbb_notifications->mark_notifications_read_by_parent(array(
+ 'quote',
+ 'bookmark',
+ 'post',
+ 'approve_post',
+ ), $topic_id, $user->data['user_id'], $post_time);
+
if ($config['load_db_lastread'] && $user->data['is_registered'])
{
$sql = 'UPDATE ' . TOPICS_TRACK_TABLE . "
@@ -4995,7 +5069,7 @@ function phpbb_build_hidden_fields_for_query_params($request, $exclude = null)
function page_header($page_title = '', $display_online_list = true, $item_id = 0, $item = 'forum')
{
global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path;
- global $phpbb_dispatcher, $request;
+ global $phpbb_dispatcher, $request, $phpbb_container;
if (defined('HEADER_INC'))
{
@@ -5184,8 +5258,26 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0
$timezone_name = $user->lang['timezones'][$timezone_name];
}
+ // Output the notifications
+ $notifications = false;
+ if ($config['load_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE)
+ {
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ $notifications = $phpbb_notifications->load_notifications(array(
+ 'all_unread' => true,
+ 'limit' => 5,
+ ));
+
+ foreach ($notifications['notifications'] as $notification)
+ {
+ $template->assign_block_vars('notifications', $notification->prepare_for_display());
+ }
+ }
+
$hidden_fields_for_jumpbox = phpbb_build_hidden_fields_for_query_params($request, array('f'));
+
// The following assigns all _common_ variables that may be used at any point in a template.
$template->assign_vars(array(
'SITENAME' => $config['sitename'],
@@ -5202,6 +5294,12 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0
'PRIVATE_MESSAGE_INFO_UNREAD' => $l_privmsgs_text_unread,
'HIDDEN_FIELDS_FOR_JUMPBOX' => $hidden_fields_for_jumpbox,
+ 'UNREAD_NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '',
+ 'NOTIFICATIONS_COUNT' => ($notifications !== false) ? $user->lang('NOTIFICATIONS_COUNT', $notifications['unread_count']) : '',
+ 'U_VIEW_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'),
+ 'U_NOTIFICATION_SETTINGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&amp;mode=notification_options'),
+ 'S_NOTIFICATIONS_DISPLAY' => $config['load_notifications'],
+
'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'],
'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'],
'S_USER_NEW' => $user->data['user_new'],
@@ -5340,7 +5438,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0
function page_footer($run_cron = true, $display_template = true, $exit_handler = true)
{
global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx;
- global $request, $phpbb_dispatcher;
+ global $request, $phpbb_dispatcher, $phpbb_admin_path;
// A listener can set this variable to `true` when it overrides this function
$page_footer_override = false;
@@ -5396,7 +5494,7 @@ function page_footer($run_cron = true, $display_template = true, $exit_handler =
'TRANSLATION_INFO' => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '',
'CREDIT_LINE' => $user->lang('POWERED_BY', '<a href="https://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Group'),
- 'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", false, true, $user->session_id) : '')
+ 'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id) : '')
);
// Call cron-type script
@@ -5583,7 +5681,7 @@ function phpbb_convert_30_dbms_to_31($dbms)
/*
$reflection = new \ReflectionClass($dbms);
-
+
if ($reflection->isSubclassOf('phpbb_db_driver'))
{
return $dbms;
diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php
index 2f3fd7bac0..d6bd9e35dd 100644
--- a/phpBB/includes/functions_acp.php
+++ b/phpBB/includes/functions_acp.php
@@ -82,16 +82,16 @@ function adm_page_header($page_title)
'T_RANKS_PATH' => "{$phpbb_root_path}{$config['ranks_path']}/",
'T_UPLOAD_PATH' => "{$phpbb_root_path}{$config['upload_path']}/",
- 'ICON_MOVE_UP' => '<img src="' . $phpbb_admin_path . 'images/icon_up.gif" alt="' . $user->lang['MOVE_UP'] . '" title="' . $user->lang['MOVE_UP'] . '" />',
- 'ICON_MOVE_UP_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_up_disabled.gif" alt="' . $user->lang['MOVE_UP'] . '" title="' . $user->lang['MOVE_UP'] . '" />',
- 'ICON_MOVE_DOWN' => '<img src="' . $phpbb_admin_path . 'images/icon_down.gif" alt="' . $user->lang['MOVE_DOWN'] . '" title="' . $user->lang['MOVE_DOWN'] . '" />',
- 'ICON_MOVE_DOWN_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_down_disabled.gif" alt="' . $user->lang['MOVE_DOWN'] . '" title="' . $user->lang['MOVE_DOWN'] . '" />',
- 'ICON_EDIT' => '<img src="' . $phpbb_admin_path . 'images/icon_edit.gif" alt="' . $user->lang['EDIT'] . '" title="' . $user->lang['EDIT'] . '" />',
- 'ICON_EDIT_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_edit_disabled.gif" alt="' . $user->lang['EDIT'] . '" title="' . $user->lang['EDIT'] . '" />',
- 'ICON_DELETE' => '<img src="' . $phpbb_admin_path . 'images/icon_delete.gif" alt="' . $user->lang['DELETE'] . '" title="' . $user->lang['DELETE'] . '" />',
- 'ICON_DELETE_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_delete_disabled.gif" alt="' . $user->lang['DELETE'] . '" title="' . $user->lang['DELETE'] . '" />',
- 'ICON_SYNC' => '<img src="' . $phpbb_admin_path . 'images/icon_sync.gif" alt="' . $user->lang['RESYNC'] . '" title="' . $user->lang['RESYNC'] . '" />',
- 'ICON_SYNC_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_sync_disabled.gif" alt="' . $user->lang['RESYNC'] . '" title="' . $user->lang['RESYNC'] . '" />',
+ 'ICON_MOVE_UP' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_up.gif" alt="' . $user->lang['MOVE_UP'] . '" title="' . $user->lang['MOVE_UP'] . '" />',
+ 'ICON_MOVE_UP_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_up_disabled.gif" alt="' . $user->lang['MOVE_UP'] . '" title="' . $user->lang['MOVE_UP'] . '" />',
+ 'ICON_MOVE_DOWN' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_down.gif" alt="' . $user->lang['MOVE_DOWN'] . '" title="' . $user->lang['MOVE_DOWN'] . '" />',
+ 'ICON_MOVE_DOWN_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_down_disabled.gif" alt="' . $user->lang['MOVE_DOWN'] . '" title="' . $user->lang['MOVE_DOWN'] . '" />',
+ 'ICON_EDIT' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_edit.gif" alt="' . $user->lang['EDIT'] . '" title="' . $user->lang['EDIT'] . '" />',
+ 'ICON_EDIT_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_edit_disabled.gif" alt="' . $user->lang['EDIT'] . '" title="' . $user->lang['EDIT'] . '" />',
+ 'ICON_DELETE' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_delete.gif" alt="' . $user->lang['DELETE'] . '" title="' . $user->lang['DELETE'] . '" />',
+ 'ICON_DELETE_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_delete_disabled.gif" alt="' . $user->lang['DELETE'] . '" title="' . $user->lang['DELETE'] . '" />',
+ 'ICON_SYNC' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_sync.gif" alt="' . $user->lang['RESYNC'] . '" title="' . $user->lang['RESYNC'] . '" />',
+ 'ICON_SYNC_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_sync_disabled.gif" alt="' . $user->lang['RESYNC'] . '" title="' . $user->lang['RESYNC'] . '" />',
'S_USER_LANG' => $user->lang['USER_LANG'],
'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'],
@@ -443,6 +443,13 @@ function validate_config_vars($config_vars, &$cfg_array, &$error)
}
break;
+ case 'email':
+ if (!preg_match('/^' . get_preg_expression('email') . '$/i', $cfg_array[$config_name]))
+ {
+ $error[] = $user->lang['EMAIL_INVALID_EMAIL'];
+ }
+ break;
+
// Absolute path
case 'script_path':
if (!$cfg_array[$config_name])
diff --git a/phpBB/includes/functions_admin.php b/phpBB/includes/functions_admin.php
index ae9dc94fc0..b844bd1966 100644
--- a/phpBB/includes/functions_admin.php
+++ b/phpBB/includes/functions_admin.php
@@ -618,7 +618,7 @@ function move_posts($post_ids, $topic_id, $auto_sync = true)
*/
function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_sync = true, $call_delete_posts = true)
{
- global $db, $config;
+ global $db, $config, $phpbb_container;
$approved_topics = 0;
$forum_ids = $topic_ids = array();
@@ -715,6 +715,14 @@ function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_s
set_config_count('num_topics', $approved_topics * (-1), true);
}
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ $phpbb_notifications->delete_notifications(array(
+ 'topic',
+ 'approve_topic',
+ 'topic_in_queue',
+ ), $topic_ids);
+
return $return;
}
@@ -723,7 +731,7 @@ function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_s
*/
function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync = true, $post_count_sync = true, $call_delete_topics = true)
{
- global $db, $config, $phpbb_root_path, $phpEx, $auth, $user;
+ global $db, $config, $phpbb_root_path, $phpEx, $auth, $user, $phpbb_container;
if ($where_type === 'range')
{
@@ -892,6 +900,16 @@ function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync =
delete_topics('topic_id', $remove_topics, $auto_sync, $post_count_sync, false);
}
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ $phpbb_notifications->delete_notifications(array(
+ 'quote',
+ 'bookmark',
+ 'post',
+ 'approve_post',
+ 'post_in_queue',
+ ), $post_ids);
+
return sizeof($post_ids);
}
@@ -2326,13 +2344,17 @@ function auto_prune($forum_id, $prune_mode, $prune_flags, $prune_days, $prune_fr
}
/**
-* Cache moderators, called whenever permissions are changed via admin_permissions. Changes of username
-* and group names must be carried through for the moderators table
+* Cache moderators. Called whenever permissions are changed
+* via admin_permissions. Changes of usernames and group names
+* must be carried through for the moderators table.
+*
+* @param phpbb_db_driver $db Database connection
+* @param phpbb_cache_driver_interface Cache driver
+* @param phpbb_auth $auth Authentication object
+* @return null
*/
-function cache_moderators()
+function phpbb_cache_moderators($db, $cache, $auth)
{
- global $db, $cache, $auth, $phpbb_root_path, $phpEx;
-
// Remove cached sql results
$cache->destroy('sql', MODERATOR_CACHE_TABLE);
@@ -2503,6 +2525,20 @@ function cache_moderators()
}
/**
+* Cache moderators. Called whenever permissions are changed
+* via admin_permissions. Changes of usernames and group names
+* must be carried through for the moderators table.
+*
+* @deprecated 3.1
+* @return null
+*/
+function cache_moderators()
+{
+ global $db, $cache, $auth;
+ return phpbb_cache_moderators($db, $cache, $auth);
+}
+
+/**
* View log
* If $log_count is set to false, we will skip counting all entries in the database.
*/
@@ -2774,12 +2810,16 @@ function view_log($mode, &$log, &$log_count, $limit = 0, $offset = 0, $forum_id
}
/**
-* Update foes - remove moderators and administrators from foe lists...
+* Removes moderators and administrators from foe lists.
+*
+* @param phpbb_db_driver $db Database connection
+* @param phpbb_auth $auth Authentication object
+* @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore
+* @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore
+* @return null
*/
-function update_foes($group_id = false, $user_id = false)
+function phpbb_update_foes($db, $auth, $group_id = false, $user_id = false)
{
- global $db, $auth;
-
// update foes for some user
if (is_array($user_id) && sizeof($user_id))
{
@@ -2889,6 +2929,20 @@ function update_foes($group_id = false, $user_id = false)
}
/**
+* Removes moderators and administrators from foe lists.
+*
+* @deprecated 3.1
+* @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore
+* @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore
+* @return null
+*/
+function update_foes($group_id = false, $user_id = false)
+{
+ global $db, $auth;
+ return phpbb_update_foes($db, $auth, $group_id, $user_id);
+}
+
+/**
* Lists inactive users
*/
function view_inactive_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_inactive_time DESC')
diff --git a/phpBB/includes/functions_container.php b/phpBB/includes/functions_container.php
index 8014574443..a3ed21c35b 100644
--- a/phpBB/includes/functions_container.php
+++ b/phpBB/includes/functions_container.php
@@ -105,6 +105,15 @@ function phpbb_create_compiled_container(array $extensions, array $passes, $phpb
return $container;
}
+/**
+* Create a compiled and dumped ContainerBuilder object
+*
+* @param array $extensions Array of Container extension objects
+* @param array $passes Array of Compiler Pass objects
+* @param string $phpbb_root_path Root path
+* @param string $php_ext PHP Extension
+* @return ContainerBuilder object (compiled)
+*/
function phpbb_create_dumped_container(array $extensions, array $passes, $phpbb_root_path, $php_ext)
{
// Check for our cached container; if it exists, use it
@@ -129,12 +138,60 @@ function phpbb_create_dumped_container(array $extensions, array $passes, $phpbb_
return $container;
}
+/**
+* Create an environment-specific ContainerBuilder object
+*
+* If debug is enabled, the container is re-compiled every time.
+* This ensures that the latest changes will always be reflected
+* during development.
+*
+* Otherwise it will get the existing dumped container and use
+* that one instead.
+*
+* @param array $extensions Array of Container extension objects
+* @param array $passes Array of Compiler Pass objects
+* @param string $phpbb_root_path Root path
+* @param string $php_ext PHP Extension
+* @return ContainerBuilder object (compiled)
+*/
function phpbb_create_dumped_container_unless_debug(array $extensions, array $passes, $phpbb_root_path, $php_ext)
{
$container_factory = defined('DEBUG') ? 'phpbb_create_compiled_container' : 'phpbb_create_dumped_container';
return $container_factory($extensions, $passes, $phpbb_root_path, $php_ext);
}
+/**
+* Create a default ContainerBuilder object
+*
+* Contains the default configuration of the phpBB container.
+*
+* @param array $extensions Array of Container extension objects
+* @param array $passes Array of Compiler Pass objects
+* @return ContainerBuilder object (compiled)
+*/
+function phpbb_create_default_container($phpbb_root_path, $php_ext)
+{
+ return phpbb_create_dumped_container_unless_debug(
+ array(
+ new phpbb_di_extension_config($phpbb_root_path . 'config.' . $php_ext),
+ new phpbb_di_extension_core($phpbb_root_path),
+ ),
+ array(
+ new phpbb_di_pass_collection_pass(),
+ new phpbb_di_pass_kernel_pass(),
+ ),
+ $phpbb_root_path,
+ $php_ext
+ );
+}
+
+/**
+* Get the filename under which the dumped container will be stored.
+*
+* @param string $phpbb_root_path Root path
+* @param string $php_ext PHP Extension
+* @return Path for dumped container
+*/
function phpbb_container_filename($phpbb_root_path, $php_ext)
{
$filename = str_replace(array('/', '.'), array('slash', 'dot'), $phpbb_root_path);
diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php
index 2b4b7eb10d..8978e3fadd 100644
--- a/phpBB/includes/functions_install.php
+++ b/phpBB/includes/functions_install.php
@@ -32,6 +32,8 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'AVAILABLE' => true,
'2.0.x' => false,
),
+ // Note: php 5.5 alpha 2 deprecated mysql.
+ // Keep mysqli before mysql in this list.
'mysqli' => array(
'LABEL' => 'MySQL with MySQLi Extension',
'SCHEMA' => 'mysql_41',
@@ -508,6 +510,9 @@ function phpbb_create_config_file_data($data, $dbms, $debug = false, $debug_test
'dbuser' => $data['dbuser'],
'dbpasswd' => htmlspecialchars_decode($data['dbpasswd']),
'table_prefix' => $data['table_prefix'],
+
+ 'adm_relative_path' => 'adm/',
+
'acm_type' => 'phpbb_cache_driver_file',
);
diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php
index d0a02567ad..821f0d970d 100644
--- a/phpBB/includes/functions_messenger.php
+++ b/phpBB/includes/functions_messenger.php
@@ -393,6 +393,28 @@ class messenger
}
/**
+ * Generates a valid message id to be used in emails
+ *
+ * @return string message id
+ */
+ function generate_message_id()
+ {
+ global $config;
+
+ $domain = 'phpbb.generated';
+ if ($config['server_name'])
+ {
+ $domain = $config['server_name'];
+ }
+ else if (!empty($_SERVER['SERVER_NAME']))
+ {
+ $domain = $_SERVER['SERVER_NAME'];
+ }
+
+ return md5(unique_id(time())) . '@' . $domain;
+ }
+
+ /**
* Return email header
*/
function build_header($to, $cc, $bcc)
@@ -418,7 +440,7 @@ class messenger
$headers[] = 'Return-Path: <' . $config['board_email'] . '>';
$headers[] = 'Sender: <' . $config['board_email'] . '>';
$headers[] = 'MIME-Version: 1.0';
- $headers[] = 'Message-ID: <' . md5(unique_id(time())) . '@' . $config['server_name'] . '>';
+ $headers[] = 'Message-ID: <' . $this->generate_message_id() . '>';
$headers[] = 'Date: ' . date('r', time());
$headers[] = 'Content-Type: text/plain; charset=UTF-8'; // format=flowed
$headers[] = 'Content-Transfer-Encoding: 8bit'; // 7bit
diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php
index 577cb58b34..f9dacae655 100644
--- a/phpBB/includes/functions_posting.php
+++ b/phpBB/includes/functions_posting.php
@@ -61,7 +61,7 @@ function generate_smilies($mode, $forum_id)
'body' => 'posting_smilies.html')
);
- generate_pagination(append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=smilies&amp;f=' . $forum_id), $smiley_count, $config['smilies_per_page'], $start);
+ generate_pagination(append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=smilies&amp;f=' . $forum_id), $smiley_count, $config['smilies_per_page'], $start);
}
$display_link = false;
@@ -1175,238 +1175,6 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id
return true;
}
-/**
-* User Notification
-*/
-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;
-
- $topic_notification = ($mode == 'reply' || $mode == 'quote') ? true : false;
- $forum_notification = ($mode == 'post') ? true : false;
-
- if (!$topic_notification && !$forum_notification)
- {
- trigger_error('NO_MODE');
- }
-
- if (($topic_notification && !$config['allow_topic_notify']) || ($forum_notification && !$config['allow_forum_notify']))
- {
- return;
- }
-
- $topic_title = ($topic_notification) ? $topic_title : $subject;
- $topic_title = censor_text($topic_title);
-
- // Exclude guests, current user and banned users from notifications
- if (!function_exists('phpbb_get_banned_user_ids'))
- {
- include($phpbb_root_path . 'includes/functions_user.' . $phpEx);
- }
- $sql_ignore_users = phpbb_get_banned_user_ids();
- $sql_ignore_users[ANONYMOUS] = ANONYMOUS;
- $sql_ignore_users[$user->data['user_id']] = $user->data['user_id'];
-
- $notify_rows = array();
-
- // -- get forum_userids || topic_userids
- $sql = 'SELECT u.user_id, u.username, u.user_email, u.user_lang, u.user_notify_type, u.user_jabber
- FROM ' . (($topic_notification) ? TOPICS_WATCH_TABLE : FORUMS_WATCH_TABLE) . ' w, ' . USERS_TABLE . ' u
- WHERE w.' . (($topic_notification) ? 'topic_id' : 'forum_id') . ' = ' . (($topic_notification) ? $topic_id : $forum_id) . '
- AND ' . $db->sql_in_set('w.user_id', $sql_ignore_users, true) . '
- AND w.notify_status = ' . NOTIFY_YES . '
- AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')
- AND u.user_id = w.user_id';
- $result = $db->sql_query($sql);
-
- while ($row = $db->sql_fetchrow($result))
- {
- $notify_user_id = (int) $row['user_id'];
- $notify_rows[$notify_user_id] = array(
- 'user_id' => $notify_user_id,
- 'username' => $row['username'],
- 'user_email' => $row['user_email'],
- 'user_jabber' => $row['user_jabber'],
- 'user_lang' => $row['user_lang'],
- 'notify_type' => ($topic_notification) ? 'topic' : 'forum',
- 'template' => ($topic_notification) ? 'topic_notify' : 'newtopic_notify',
- 'method' => $row['user_notify_type'],
- 'allowed' => false
- );
-
- // Add users who have been already notified to ignore list
- $sql_ignore_users[$notify_user_id] = $notify_user_id;
- }
- $db->sql_freeresult($result);
-
- // forum notification is sent to those not already receiving topic notifications
- if ($topic_notification)
- {
- $sql = 'SELECT u.user_id, u.username, u.user_email, u.user_lang, u.user_notify_type, u.user_jabber
- FROM ' . FORUMS_WATCH_TABLE . ' fw, ' . USERS_TABLE . " u
- WHERE fw.forum_id = $forum_id
- AND " . $db->sql_in_set('fw.user_id', $sql_ignore_users, true) . '
- AND fw.notify_status = ' . NOTIFY_YES . '
- AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')
- AND u.user_id = fw.user_id';
- $result = $db->sql_query($sql);
-
- while ($row = $db->sql_fetchrow($result))
- {
- $notify_user_id = (int) $row['user_id'];
- $notify_rows[$notify_user_id] = array(
- 'user_id' => $notify_user_id,
- 'username' => $row['username'],
- 'user_email' => $row['user_email'],
- 'user_jabber' => $row['user_jabber'],
- 'user_lang' => $row['user_lang'],
- 'notify_type' => 'forum',
- 'template' => 'forum_notify',
- 'method' => $row['user_notify_type'],
- 'allowed' => false
- );
- }
- $db->sql_freeresult($result);
- }
-
- if (!sizeof($notify_rows))
- {
- return;
- }
-
- // Make sure users are allowed to read the forum
- foreach ($auth->acl_get_list(array_keys($notify_rows), 'f_read', $forum_id) as $forum_id => $forum_ary)
- {
- foreach ($forum_ary as $auth_option => $user_ary)
- {
- foreach ($user_ary as $user_id)
- {
- $notify_rows[$user_id]['allowed'] = true;
- }
- }
- }
-
- // Now, we have to do a little step before really sending, we need to distinguish our users a little bit. ;)
- $msg_users = $delete_ids = $update_notification = array();
- foreach ($notify_rows as $user_id => $row)
- {
- if (!$row['allowed'] || !trim($row['user_email']))
- {
- $delete_ids[$row['notify_type']][] = $row['user_id'];
- }
- else
- {
- $msg_users[] = $row;
- $update_notification[$row['notify_type']][] = $row['user_id'];
-
- /*
- * We also update the forums watch table for this user when we are
- * sending out a topic notification to prevent sending out another
- * notification in case this user is also subscribed to the forum
- * this topic was posted in.
- * Since an UPDATE query is used, this has no effect on users only
- * subscribed to the topic (i.e. no row is created) and should not
- * be a performance issue.
- */
- if ($row['notify_type'] === 'topic')
- {
- $update_notification['forum'][] = $row['user_id'];
- }
- }
- }
- unset($notify_rows);
-
- // Now, we are able to really send out notifications
- if (sizeof($msg_users))
- {
- include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx);
- $messenger = new messenger();
-
- $msg_list_ary = array();
- foreach ($msg_users as $row)
- {
- $pos = (!isset($msg_list_ary[$row['template']])) ? 0 : sizeof($msg_list_ary[$row['template']]);
-
- $msg_list_ary[$row['template']][$pos]['method'] = $row['method'];
- $msg_list_ary[$row['template']][$pos]['email'] = $row['user_email'];
- $msg_list_ary[$row['template']][$pos]['jabber'] = $row['user_jabber'];
- $msg_list_ary[$row['template']][$pos]['name'] = $row['username'];
- $msg_list_ary[$row['template']][$pos]['lang'] = $row['user_lang'];
- $msg_list_ary[$row['template']][$pos]['user_id']= $row['user_id'];
- }
- unset($msg_users);
-
- foreach ($msg_list_ary as $email_template => $email_list)
- {
- foreach ($email_list as $addr)
- {
- $messenger->template($email_template, $addr['lang']);
-
- $messenger->to($addr['email'], $addr['name']);
- $messenger->im($addr['jabber'], $addr['name']);
-
- $messenger->assign_vars(array(
- '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",
- 'U_NEWEST_POST' => generate_board_url() . "/viewtopic.$phpEx?f=$forum_id&t=$topic_id&p=$post_id&e=$post_id",
- 'U_STOP_WATCHING_TOPIC' => generate_board_url() . "/viewtopic.$phpEx?uid={$addr['user_id']}&f=$forum_id&t=$topic_id&unwatch=topic",
- 'U_STOP_WATCHING_FORUM' => generate_board_url() . "/viewforum.$phpEx?uid={$addr['user_id']}&f=$forum_id&unwatch=forum",
- ));
-
- $messenger->send($addr['method']);
- }
- }
- unset($msg_list_ary);
-
- $messenger->save_queue();
- }
-
- // Handle the DB updates
- $db->sql_transaction('begin');
-
- if (!empty($update_notification['topic']))
- {
- $sql = 'UPDATE ' . TOPICS_WATCH_TABLE . '
- SET notify_status = ' . NOTIFY_NO . "
- WHERE topic_id = $topic_id
- AND " . $db->sql_in_set('user_id', $update_notification['topic']);
- $db->sql_query($sql);
- }
-
- if (!empty($update_notification['forum']))
- {
- $sql = 'UPDATE ' . FORUMS_WATCH_TABLE . '
- SET notify_status = ' . NOTIFY_NO . "
- WHERE forum_id = $forum_id
- AND " . $db->sql_in_set('user_id', $update_notification['forum']);
- $db->sql_query($sql);
- }
-
- // Now delete the user_ids not authorised to receive notifications on this topic/forum
- if (!empty($delete_ids['topic']))
- {
- $sql = 'DELETE FROM ' . TOPICS_WATCH_TABLE . "
- WHERE topic_id = $topic_id
- AND " . $db->sql_in_set('user_id', $delete_ids['topic']);
- $db->sql_query($sql);
- }
-
- if (!empty($delete_ids['forum']))
- {
- $sql = 'DELETE FROM ' . FORUMS_WATCH_TABLE . "
- WHERE forum_id = $forum_id
- AND " . $db->sql_in_set('user_id', $delete_ids['forum']);
- $db->sql_query($sql);
- }
-
- $db->sql_transaction('commit');
-}
-
//
// Post handling functions
//
@@ -1698,7 +1466,7 @@ function delete_post($forum_id, $topic_id, $post_id, &$data, $is_soft = false, $
*/
function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $update_message = true, $update_search_index = true)
{
- global $db, $auth, $user, $config, $phpEx, $template, $phpbb_root_path;
+ global $db, $auth, $user, $config, $phpEx, $template, $phpbb_root_path, $phpbb_container;
// We do not handle erasing posts here
if ($mode == 'delete')
@@ -2402,10 +2170,76 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u
}
// Send Notifications
- if (($mode == 'reply' || $mode == 'quote' || $mode == 'post') && $post_visibility == ITEM_APPROVED)
+ $notification_data = array_merge($data, array(
+ 'topic_title' => (isset($data['topic_title'])) ? $data['topic_title'] : $subject,
+ 'post_username' => $username,
+ 'poster_id' => $poster_id,
+ 'post_text' => $data['message'],
+ 'post_time' => $current_time,
+ 'post_subject' => $subject,
+ ));
+
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ if ($post_visibility == ITEM_APPROVED)
{
- $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);
+ switch ($mode)
+ {
+ case 'post':
+ $phpbb_notifications->add_notifications(array(
+ 'quote',
+ 'topic',
+ ), $notification_data);
+ break;
+
+ case 'reply':
+ case 'quote':
+ $phpbb_notifications->add_notifications(array(
+ 'quote',
+ 'bookmark',
+ 'post',
+ ), $notification_data);
+ break;
+
+ case 'edit_topic':
+ case 'edit_first_post':
+ case 'edit':
+ case 'edit_last_post':
+ $phpbb_notifications->update_notifications(array(
+ 'quote',
+ 'bookmark',
+ 'topic',
+ 'post',
+ ), $notification_data);
+ break;
+ }
+ }
+ else
+ {
+ switch ($mode)
+ {
+ case 'post':
+ $phpbb_notifications->add_notifications('topic_in_queue', $notification_data);
+ break;
+
+ case 'reply':
+ case 'quote':
+ $phpbb_notifications->add_notifications('post_in_queue', $notification_data);
+ break;
+
+ case 'edit_topic':
+ case 'edit_first_post':
+ case 'edit':
+ case 'edit_last_post':
+ $phpbb_notifications->delete_notifications('topic', $data['topic_id']);
+
+ $phpbb_notifications->delete_notifications(array(
+ 'quote',
+ 'bookmark',
+ 'post',
+ ), $data['post_id']);
+ break;
+ }
}
$params = $add_anchor = '';
diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php
index ba939d490e..14278a2529 100644
--- a/phpBB/includes/functions_privmsgs.php
+++ b/phpBB/includes/functions_privmsgs.php
@@ -876,7 +876,11 @@ function update_unread_status($unread, $msg_id, $user_id, $folder_id)
return;
}
- global $db, $user;
+ global $db, $user, $phpbb_container;
+
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ $phpbb_notifications->mark_notifications_read('pm', $msg_id, $user_id);
$sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . "
SET pm_unread = 0
@@ -981,7 +985,7 @@ function handle_mark_actions($user_id, $mark_action)
*/
function delete_pm($user_id, $msg_ids, $folder_id)
{
- global $db, $user, $phpbb_root_path, $phpEx;
+ global $db, $user, $phpbb_root_path, $phpEx, $phpbb_container;
$user_id = (int) $user_id;
$folder_id = (int) $folder_id;
@@ -1093,6 +1097,10 @@ function delete_pm($user_id, $msg_ids, $folder_id)
$user->data['user_unread_privmsg'] -= $num_unread;
}
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ $phpbb_notifications->delete_notifications('pm', array_keys($delete_rows));
+
// Now we have to check which messages we can delete completely
$sql = 'SELECT msg_id
FROM ' . PRIVMSGS_TO_TABLE . '
@@ -1157,7 +1165,7 @@ function phpbb_delete_user_pms($user_id)
*/
function phpbb_delete_users_pms($user_ids)
{
- global $db, $user, $phpbb_root_path, $phpEx;
+ global $db, $user, $phpbb_root_path, $phpEx, $phpbb_container;
$user_id_sql = $db->sql_in_set('user_id', $user_ids);
$author_id_sql = $db->sql_in_set('author_id', $user_ids);
@@ -1202,6 +1210,8 @@ function phpbb_delete_users_pms($user_ids)
$db->sql_transaction('begin');
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
if (!empty($undelivered_msg))
{
// A pm is delivered, if for any recipient the message was moved
@@ -1270,6 +1280,8 @@ function phpbb_delete_users_pms($user_ids)
WHERE folder_id = ' . PRIVMSGS_NO_BOX . '
AND ' . $db->sql_in_set('msg_id', $delivered_msg);
$db->sql_query($sql);
+
+ $phpbb_notifications->delete_notifications('pm', $delivered_msg);
}
if (!empty($undelivered_msg))
@@ -1281,6 +1293,8 @@ function phpbb_delete_users_pms($user_ids)
$sql = 'DELETE FROM ' . PRIVMSGS_TABLE . '
WHERE ' . $db->sql_in_set('msg_id', $undelivered_msg);
$db->sql_query($sql);
+
+ $phpbb_notifications->delete_notifications('pm', $undelivered_msg);
}
}
@@ -1323,6 +1337,8 @@ function phpbb_delete_users_pms($user_ids)
$sql = 'DELETE FROM ' . PRIVMSGS_TABLE . '
WHERE ' . $db->sql_in_set('msg_id', $delete_ids);
$db->sql_query($sql);
+
+ $phpbb_notifications->delete_notifications('pm', $delete_ids);
}
}
@@ -1559,7 +1575,7 @@ function get_folder_status($folder_id, $folder)
*/
function submit_pm($mode, $subject, &$data, $put_in_outbox = true)
{
- global $db, $auth, $config, $phpEx, $template, $user, $phpbb_root_path;
+ global $db, $auth, $config, $phpEx, $template, $user, $phpbb_root_path, $phpbb_container;
// We do not handle erasing pms here
if ($mode == 'delete')
@@ -1859,95 +1875,23 @@ function submit_pm($mode, $subject, &$data, $put_in_outbox = true)
$db->sql_transaction('commit');
// Send Notifications
- if ($mode != 'edit')
- {
- pm_notification($mode, $data['from_username'], $recipients, $subject, $data['message'], $data['msg_id']);
- }
-
- return $data['msg_id'];
-}
-
-/**
-* PM Notification
-*/
-function pm_notification($mode, $author, $recipients, $subject, $message, $msg_id)
-{
- global $db, $user, $config, $phpbb_root_path, $phpEx, $auth;
-
- $subject = censor_text($subject);
-
- // Exclude guests, current user and banned users from notifications
- unset($recipients[ANONYMOUS], $recipients[$user->data['user_id']]);
-
- if (!sizeof($recipients))
- {
- return;
- }
-
- if (!function_exists('phpbb_get_banned_user_ids'))
- {
- include($phpbb_root_path . 'includes/functions_user.' . $phpEx);
- }
- $banned_users = phpbb_get_banned_user_ids(array_keys($recipients));
- $recipients = array_diff(array_keys($recipients), $banned_users);
-
- if (!sizeof($recipients))
- {
- return;
- }
+ $pm_data = array_merge($data, array(
+ 'message_subject' => $subject,
+ 'recipients' => $recipients,
+ ));
- $sql = 'SELECT user_id, username, user_email, user_lang, user_notify_pm, user_notify_type, user_jabber
- FROM ' . USERS_TABLE . '
- WHERE ' . $db->sql_in_set('user_id', $recipients);
- $result = $db->sql_query($sql);
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
- $msg_list_ary = array();
- while ($row = $db->sql_fetchrow($result))
+ if ($mode == 'edit')
{
- if ($row['user_notify_pm'] == 1 && trim($row['user_email']))
- {
- $msg_list_ary[] = array(
- 'method' => $row['user_notify_type'],
- 'email' => $row['user_email'],
- 'jabber' => $row['user_jabber'],
- 'name' => $row['username'],
- 'lang' => $row['user_lang']
- );
- }
+ $phpbb_notifications->update_notifications('pm', $pm_data);
}
- $db->sql_freeresult($result);
-
- if (!sizeof($msg_list_ary))
+ else
{
- return;
+ $phpbb_notifications->add_notifications('pm', $pm_data);
}
- include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx);
- $messenger = new messenger();
-
- foreach ($msg_list_ary as $pos => $addr)
- {
- $messenger->template('privmsg_notify', $addr['lang']);
-
- $messenger->to($addr['email'], $addr['name']);
- $messenger->im($addr['jabber'], $addr['name']);
-
- $messenger->assign_vars(array(
- 'SUBJECT' => htmlspecialchars_decode($subject),
- 'AUTHOR_NAME' => htmlspecialchars_decode($author),
- 'USERNAME' => htmlspecialchars_decode($addr['name']),
-
- 'U_INBOX' => generate_board_url() . "/ucp.$phpEx?i=pm&folder=inbox",
- 'U_VIEW_MESSAGE' => generate_board_url() . "/ucp.$phpEx?i=pm&mode=view&p=$msg_id",
- ));
-
- $messenger->send($addr['method']);
- }
- unset($msg_list_ary);
-
- $messenger->save_queue();
-
- unset($messenger);
+ return $data['msg_id'];
}
/**
diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php
index 8f9c9198f4..b7878ddfc7 100644
--- a/phpBB/includes/functions_user.php
+++ b/phpBB/includes/functions_user.php
@@ -2698,12 +2698,12 @@ function group_create(&$group_id, $type, $name, $desc, $group_attributes, $allow
}
$db->sql_freeresult($result);
- if (isset($sql_ary['group_avatar']) && !$sql_ary['group_avatar'])
+ if (isset($sql_ary['group_avatar']))
{
remove_default_avatar($group_id, $user_ary);
}
- if (isset($sql_ary['group_rank']) && !$sql_ary['group_rank'])
+ if (isset($sql_ary['group_rank']))
{
remove_default_rank($group_id, $user_ary);
}
@@ -2842,7 +2842,7 @@ function avatar_remove_db($avatar_name)
*/
function group_delete($group_id, $group_name = false)
{
- global $db, $phpbb_root_path, $phpEx, $phpbb_dispatcher;
+ global $db, $cache, $auth, $phpbb_root_path, $phpEx, $phpbb_dispatcher;
if (!$group_name)
{
@@ -2913,12 +2913,12 @@ function group_delete($group_id, $group_name = false)
extract($phpbb_dispatcher->trigger_event('core.delete_group_after', compact($vars)));
// Re-cache moderators
- if (!function_exists('cache_moderators'))
+ if (!function_exists('phpbb_cache_moderators'))
{
include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
}
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
add_log('admin', 'LOG_GROUP_DELETE', $group_name);
@@ -3208,8 +3208,8 @@ function remove_default_avatar($group_id, $user_ids)
user_avatar_width = 0,
user_avatar_height = 0
WHERE group_id = " . (int) $group_id . "
- AND user_avatar = '" . $db->sql_escape($row['group_avatar']) . "'
- AND " . $db->sql_in_set('user_id', $user_ids);
+ AND user_avatar = '" . $db->sql_escape($row['group_avatar']) . "'
+ AND " . $db->sql_in_set('user_id', $user_ids);
$db->sql_query($sql);
}
@@ -3246,9 +3246,9 @@ function remove_default_rank($group_id, $user_ids)
$sql = 'UPDATE ' . USERS_TABLE . '
SET user_rank = 0
WHERE group_id = ' . (int)$group_id . '
- AND user_rank <> 0
- AND user_rank = ' . (int)$row['group_rank'] . '
- AND ' . $db->sql_in_set('user_id', $user_ids);
+ AND user_rank <> 0
+ AND user_rank = ' . (int)$row['group_rank'] . '
+ AND ' . $db->sql_in_set('user_id', $user_ids);
$db->sql_query($sql);
}
@@ -3277,7 +3277,8 @@ function group_user_attributes($action, $group_id, $user_id_ary = false, $userna
case 'demote':
case 'promote':
- $sql = 'SELECT user_id FROM ' . USER_GROUP_TABLE . "
+ $sql = 'SELECT user_id
+ FROM ' . USER_GROUP_TABLE . "
WHERE group_id = $group_id
AND user_pending = 1
AND " . $db->sql_in_set('user_id', $user_id_ary);
@@ -3375,7 +3376,8 @@ function group_user_attributes($action, $group_id, $user_id_ary = false, $userna
return 'NO_USERS';
}
- $sql = 'SELECT user_id, group_id FROM ' . USERS_TABLE . '
+ $sql = 'SELECT user_id, group_id
+ FROM ' . USERS_TABLE . '
WHERE ' . $db->sql_in_set('user_id', $user_id_ary, false, true);
$result = $db->sql_query($sql);
@@ -3463,7 +3465,7 @@ function group_validate_groupname($group_id, $group_name)
*/
function group_set_user_default($group_id, $user_id_ary, $group_attributes = false, $update_listing = false)
{
- global $cache, $db, $phpbb_dispatcher;
+ global $phpbb_container, $db, $phpbb_dispatcher;
if (empty($user_id_ary))
{
@@ -3509,45 +3511,69 @@ function group_set_user_default($group_id, $user_id_ary, $group_attributes = fal
}
}
- // Before we update the user attributes, we will make a list of those having now the group avatar assigned
- if (isset($sql_ary['user_avatar']))
+ $updated_sql_ary = $sql_ary;
+
+ // Before we update the user attributes, we will update the rank for users that don't have a custom rank
+ if (isset($sql_ary['user_rank']))
{
- // Ok, get the original avatar data from users having an uploaded one (we need to remove these from the filesystem)
- $sql = 'SELECT user_id, group_id, user_avatar
- FROM ' . USERS_TABLE . '
- WHERE ' . $db->sql_in_set('user_id', $user_id_ary) . '
- AND user_avatar_type = ' . AVATAR_UPLOAD;
- $result = $db->sql_query($sql);
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET ' . $db->sql_build_array('UPDATE', array('user_rank' => $sql_ary['user_rank'])) . '
+ WHERE user_rank = 0
+ AND ' . $db->sql_in_set('user_id', $user_id_ary);
+ $db->sql_query($sql);
+ unset($sql_ary['user_rank']);
+ }
- while ($row = $db->sql_fetchrow($result))
+ // Before we update the user attributes, we will update the avatar for users that don't have a custom avatar
+ $avatar_options = array('user_avatar', 'user_avatar_type', 'user_avatar_height', 'user_avatar_width');
+
+ if (isset($sql_ary['user_avatar']))
+ {
+ $avatar_sql_ary = array();
+ foreach ($avatar_options as $avatar_option)
{
- avatar_delete('user', $row);
- }
- $db->sql_freeresult($result);
+ if (isset($sql_ary[$avatar_option]))
+ {
+ $avatar_sql_ary[$avatar_option] = $sql_ary[$avatar_option];
+ }
+ }
+
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET ' . $db->sql_build_array('UPDATE', $avatar_sql_ary) . "
+ WHERE user_avatar = ''
+ AND " . $db->sql_in_set('user_id', $user_id_ary);
+ $db->sql_query($sql);
}
- else
+
+ // Remove the avatar options, as we already updated them
+ foreach ($avatar_options as $avatar_option)
{
- unset($sql_ary['user_avatar_type']);
- unset($sql_ary['user_avatar_height']);
- unset($sql_ary['user_avatar_width']);
+ unset($sql_ary[$avatar_option]);
}
- $sql = 'UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
- WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
- $db->sql_query($sql);
+ if (!empty($sql_ary))
+ {
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
+ WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
+ $db->sql_query($sql);
+ }
if (isset($sql_ary['user_colour']))
{
// Update any cached colour information for these users
- $sql = 'UPDATE ' . FORUMS_TABLE . " SET forum_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
+ $sql = 'UPDATE ' . FORUMS_TABLE . "
+ SET forum_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
WHERE " . $db->sql_in_set('forum_last_poster_id', $user_id_ary);
$db->sql_query($sql);
- $sql = 'UPDATE ' . TOPICS_TABLE . " SET topic_first_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
+ $sql = 'UPDATE ' . TOPICS_TABLE . "
+ SET topic_first_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
WHERE " . $db->sql_in_set('topic_poster', $user_id_ary);
$db->sql_query($sql);
- $sql = 'UPDATE ' . TOPICS_TABLE . " SET topic_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
+ $sql = 'UPDATE ' . TOPICS_TABLE . "
+ SET topic_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
WHERE " . $db->sql_in_set('topic_last_poster_id', $user_id_ary);
$db->sql_query($sql);
@@ -3559,6 +3585,9 @@ function group_set_user_default($group_id, $user_id_ary, $group_attributes = fal
}
}
+ // Make all values available for the event
+ $sql_ary = $updated_sql_ary;
+
/**
* Event when the default group is set for an array of users
*
@@ -3579,7 +3608,7 @@ function group_set_user_default($group_id, $user_id_ary, $group_attributes = fal
}
// Because some tables/caches use usercolour-specific data we need to purge this here.
- $cache->destroy('sql', MODERATOR_CACHE_TABLE);
+ $phpbb_container->get('cache.driver')->destroy('sql', MODERATOR_CACHE_TABLE);
}
/**
@@ -3678,7 +3707,7 @@ function group_memberships($group_id_ary = false, $user_id_ary = false, $return_
*/
function group_update_listings($group_id)
{
- global $auth;
+ global $db, $cache, $auth;
$hold_ary = $auth->acl_group_raw_data($group_id, array('a_', 'm_'));
@@ -3720,22 +3749,22 @@ function group_update_listings($group_id)
if ($mod_permissions)
{
- if (!function_exists('cache_moderators'))
+ if (!function_exists('phpbb_cache_moderators'))
{
global $phpbb_root_path, $phpEx;
include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
}
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
}
if ($mod_permissions || $admin_permissions)
{
- if (!function_exists('update_foes'))
+ if (!function_exists('phpbb_update_foes'))
{
global $phpbb_root_path, $phpEx;
include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
}
- update_foes(array($group_id));
+ phpbb_update_foes($db, $auth, array($group_id));
}
}
diff --git a/phpBB/includes/hook/finder.php b/phpBB/includes/hook/finder.php
new file mode 100644
index 0000000000..065e685514
--- /dev/null
+++ b/phpBB/includes/hook/finder.php
@@ -0,0 +1,84 @@
+<?php
+/**
+*
+* @package extension
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* The hook finder locates installed hooks.
+*
+* @package phpBB3
+*/
+class phpbb_hook_finder
+{
+ protected $phpbb_root_path;
+ protected $cache;
+ protected $php_ext;
+
+ /**
+ * Creates a new finder instance.
+ *
+ * @param string $phpbb_root_path Path to the phpbb root directory
+ * @param string $php_ext php file extension
+ * @param phpbb_cache_driver_interface $cache A cache instance or null
+ */
+ public function __construct($phpbb_root_path, $php_ext, phpbb_cache_driver_interface $cache = null)
+ {
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->cache = $cache;
+ $this->php_ext = $php_ext;
+ }
+
+ /**
+ * Finds all hook files.
+ *
+ * @param bool $cache Whether the result should be cached
+ * @return array An array of paths to found hook files
+ */
+ public function find($cache = true)
+ {
+ if (!defined('DEBUG') && $cache && $this->cache)
+ {
+ $hook_files = $this->cache->get('_hooks');
+ if ($hook_files !== false)
+ {
+ return $hook_files;
+ }
+ }
+
+ $hook_files = array();
+
+ // Now search for hooks...
+ $dh = @opendir($this->phpbb_root_path . 'includes/hooks/');
+
+ if ($dh)
+ {
+ while (($file = readdir($dh)) !== false)
+ {
+ if (strpos($file, 'hook_') === 0 && substr($file, -(strlen($this->php_ext) + 1)) === '.' . $this->php_ext)
+ {
+ $hook_files[] = substr($file, 0, -(strlen($this->php_ext) + 1));
+ }
+ }
+ closedir($dh);
+ }
+
+ if ($cache && $this->cache)
+ {
+ $this->cache->put('_hooks', $hook_files);
+ }
+
+ return $hook_files;
+ }
+}
diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php
index be18dba944..99ff397a66 100644
--- a/phpBB/includes/mcp/mcp_pm_reports.php
+++ b/phpBB/includes/mcp/mcp_pm_reports.php
@@ -33,7 +33,7 @@ class mcp_pm_reports
function main($id, $mode)
{
global $auth, $db, $user, $template, $cache;
- global $config, $phpbb_root_path, $phpEx, $action;
+ global $config, $phpbb_root_path, $phpEx, $action, $phpbb_container;
include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx);
include_once($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx);
@@ -89,6 +89,10 @@ class mcp_pm_reports
trigger_error('NO_REPORT');
}
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ $phpbb_notifications->mark_notifications_read_by_parent('report_pm', $report_id, $user->data['user_id']);
+
$pm_id = $report['pm_id'];
$report_id = $report['report_id'];
@@ -122,6 +126,7 @@ class mcp_pm_reports
$message = bbcode_nl2br($message);
$message = smiley_text($message);
+ $report['report_text'] = make_clickable(bbcode_nl2br($report['report_text']));
if ($pm_info['message_attachment'] && $auth->acl_get('u_pm_download'))
{
diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php
index d9ec10e6da..6f9f16bde4 100644
--- a/phpBB/includes/mcp/mcp_queue.php
+++ b/phpBB/includes/mcp/mcp_queue.php
@@ -33,7 +33,7 @@ class mcp_queue
public function main($id, $mode)
{
global $auth, $db, $user, $template, $cache, $request;
- global $config, $phpbb_root_path, $phpEx, $action;
+ global $config, $phpbb_root_path, $phpEx, $action, $phpbb_container;
include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx);
@@ -149,12 +149,16 @@ class mcp_queue
$post_id = request_var('p', 0);
$topic_id = request_var('t', 0);
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
if ($topic_id)
{
$topic_info = get_topic_data(array($topic_id), 'm_approve');
if (isset($topic_info[$topic_id]['topic_first_post_id']))
{
$post_id = (int) $topic_info[$topic_id]['topic_first_post_id'];
+
+ $phpbb_notifications->mark_notifications_read('topic_in_queue', $topic_id, $user->data['user_id']);
}
else
{
@@ -162,6 +166,8 @@ class mcp_queue
}
}
+ $phpbb_notifications->mark_notifications_read('post_in_queue', $post_id, $user->data['user_id']);
+
$post_info = get_post_data(array($post_id), 'm_approve', true);
if (!sizeof($post_info))
@@ -567,8 +573,8 @@ class mcp_queue
*/
static public function approve_posts($action, $post_id_list, $id, $mode)
{
- global $db, $template, $user, $config;
- global $phpEx, $phpbb_root_path, $request;
+ global $db, $template, $user, $config, $request, $phpbb_container;
+ global $phpEx, $phpbb_root_path;
if (!check_ids($post_id_list, POSTS_TABLE, 'post_id', array('m_approve')))
{
diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php
index 7b038b476b..72400ce623 100644
--- a/phpBB/includes/mcp/mcp_reports.php
+++ b/phpBB/includes/mcp/mcp_reports.php
@@ -33,7 +33,7 @@ class mcp_reports
function main($id, $mode)
{
global $auth, $db, $user, $template, $cache;
- global $config, $phpbb_root_path, $phpEx, $action;
+ global $config, $phpbb_root_path, $phpEx, $action, $phpbb_container;
include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx);
@@ -71,7 +71,7 @@ class mcp_reports
// closed reports are accessed by report id
$report_id = request_var('r', 0);
- $sql = 'SELECT r.post_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, r.reported_post_text, r.reported_post_uid, r.reported_post_bitfield, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour
+ $sql = 'SELECT r.post_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, r.reported_post_text, r.reported_post_uid, r.reported_post_bitfield, r.reported_post_enable_magic_url, r.reported_post_enable_smilies, r.reported_post_enable_bbcode, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour
FROM ' . REPORTS_TABLE . ' r, ' . REPORTS_REASONS_TABLE . ' rr, ' . USERS_TABLE . ' u
WHERE ' . (($report_id) ? 'r.report_id = ' . $report_id : "r.post_id = $post_id") . '
AND rr.reason_id = r.reason_id
@@ -87,6 +87,10 @@ class mcp_reports
trigger_error('NO_REPORT');
}
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ $phpbb_notifications->mark_notifications_read('report_post', $post_id, $user->data['user_id']);
+
if (!$report_id && $report['report_closed'])
{
trigger_error('REPORT_CLOSED');
@@ -94,6 +98,10 @@ class mcp_reports
$post_id = $report['post_id'];
$report_id = $report['report_id'];
+
+ $parse_post_flags = $report['reported_post_enable_bbcode'] ? OPTION_FLAG_BBCODE : 0;
+ $parse_post_flags += $report['reported_post_enable_smilies'] ? OPTION_FLAG_SMILIES : 0;
+ $parse_post_flags += $report['reported_post_enable_magic_url'] ? OPTION_FLAG_LINKS : 0;
$post_info = get_post_data(array($post_id), 'm_report', true);
@@ -136,18 +144,7 @@ class mcp_reports
$post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false;
- // Process message, leave it uncensored
- $message = $post_info['post_text'];
- if ($post_info['bbcode_bitfield'])
- {
- include_once($phpbb_root_path . 'includes/bbcode.' . $phpEx);
- $bbcode = new bbcode($post_info['bbcode_bitfield']);
- $bbcode->bbcode_second_pass($message, $post_info['bbcode_uid'], $post_info['bbcode_bitfield']);
- }
-
- $message = bbcode_nl2br($message);
- $message = smiley_text($message);
$report['report_text'] = make_clickable(bbcode_nl2br($report['report_text']));
if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id']))
@@ -168,7 +165,7 @@ class mcp_reports
if (sizeof($attachments))
{
$update_count = array();
- parse_attachments($post_info['forum_id'], $message, $attachments, $update_count);
+ parse_attachments($post_info['forum_id'], $report['reported_post_text'], $attachments, $update_count);
}
// Display not already displayed Attachments for this post, we already parsed them. ;)
@@ -227,7 +224,7 @@ class mcp_reports
'REPORTER_NAME' => get_username_string('username', $report['user_id'], $report['username'], $report['user_colour']),
'U_VIEW_REPORTER_PROFILE' => get_username_string('profile', $report['user_id'], $report['username'], $report['user_colour']),
- 'POST_PREVIEW' => generate_text_for_display($report['reported_post_text'], $report['reported_post_uid'], $report['reported_post_bitfield'], OPTION_FLAG_BBCODE | OPTION_FLAG_SMILIES, false),
+ 'POST_PREVIEW' => generate_text_for_display($report['reported_post_text'], $report['reported_post_uid'], $report['reported_post_bitfield'], $parse_post_flags, false),
'POST_SUBJECT' => ($post_info['post_subject']) ? $post_info['post_subject'] : $user->lang['NO_SUBJECT'],
'POST_DATE' => $user->format_date($post_info['post_time']),
'POST_IP' => $post_info['poster_ip'],
@@ -443,7 +440,7 @@ class mcp_reports
function close_report($report_id_list, $mode, $action, $pm = false)
{
global $db, $template, $user, $config, $auth;
- global $phpEx, $phpbb_root_path;
+ global $phpEx, $phpbb_root_path, $phpbb_container;
$pm_where = ($pm) ? ' AND r.post_id = 0 ' : ' AND r.pm_id = 0 ';
$id_column = ($pm) ? 'pm_id' : 'post_id';
@@ -629,11 +626,11 @@ function close_report($report_id_list, $mode, $action, $pm = false)
}
}
- $messenger = new messenger();
-
// Notify reporters
if (sizeof($notify_reporters))
{
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
foreach ($notify_reporters as $report_id => $reporter)
{
if ($reporter['user_id'] == ANONYMOUS)
@@ -643,30 +640,25 @@ function close_report($report_id_list, $mode, $action, $pm = false)
$post_id = $reporter[$id_column];
- $messenger->template((($pm) ? 'pm_report_' : 'report_') . $action . 'd', $reporter['user_lang']);
-
- $messenger->to($reporter['user_email'], $reporter['username']);
- $messenger->im($reporter['user_jabber'], $reporter['username']);
-
if ($pm)
{
- $messenger->assign_vars(array(
- 'USERNAME' => htmlspecialchars_decode($reporter['username']),
- 'CLOSER_NAME' => htmlspecialchars_decode($user->data['username']),
- 'PM_SUBJECT' => htmlspecialchars_decode(censor_text($post_info[$post_id]['message_subject'])),
- ));
+ $phpbb_notifications->add_notifications('report_pm_closed', array_merge($post_info[$post_id], array(
+ 'reporter' => $reporter['user_id'],
+ 'closer_id' => $user->data['user_id'],
+ 'from_user_id' => $post_info[$post_id]['author_id'],
+ )));
+
+ $phpbb_notifications->delete_notifications('report_pm', $post_id);
}
else
{
- $messenger->assign_vars(array(
- 'USERNAME' => htmlspecialchars_decode($reporter['username']),
- 'CLOSER_NAME' => htmlspecialchars_decode($user->data['username']),
- 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($post_info[$post_id]['post_subject'])),
- 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($post_info[$post_id]['topic_title'])))
- );
- }
+ $phpbb_notifications->add_notifications('report_post_closed', array_merge($post_info[$post_id], array(
+ 'reporter' => $reporter['user_id'],
+ 'closer_id' => $user->data['user_id'],
+ )));
- $messenger->send($reporter['user_notify_type']);
+ $phpbb_notifications->delete_notifications('report_post', $post_id);
+ }
}
}
@@ -681,8 +673,6 @@ function close_report($report_id_list, $mode, $action, $pm = false)
unset($notify_reporters, $post_info, $reports);
- $messenger->save_queue();
-
$success_msg = (sizeof($report_id_list) == 1) ? "{$pm_prefix}REPORT_" . strtoupper($action) . 'D_SUCCESS' : "{$pm_prefix}REPORTS_" . strtoupper($action) . 'D_SUCCESS';
}
else
diff --git a/phpBB/includes/notification/manager.php b/phpBB/includes/notification/manager.php
new file mode 100644
index 0000000000..ff83d4bb37
--- /dev/null
+++ b/phpBB/includes/notification/manager.php
@@ -0,0 +1,853 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Notifications service class
+* @package notifications
+*/
+class phpbb_notification_manager
+{
+ /** @var array */
+ protected $notification_types;
+
+ /** @var array */
+ protected $notification_methods;
+
+ /** @var ContainerBuilder */
+ protected $phpbb_container;
+
+ /** @var phpbb_user_loader */
+ protected $user_loader;
+
+ /** @var phpbb_db_driver */
+ protected $db;
+
+ /** @var phpbb_user */
+ protected $user;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /** @var string */
+ protected $notification_types_table;
+
+ /** @var string */
+ protected $notifications_table;
+
+ /** @var string */
+ protected $user_notifications_table;
+
+ /**
+ * Notification Constructor
+ *
+ * @param array $notification_types
+ * @param array $notification_methods
+ * @param ContainerBuilder $phpbb_container
+ * @param phpbb_user_loader $user_loader
+ * @param phpbb_db_driver $db
+ * @param phpbb_user $user
+ * @param string $phpbb_root_path
+ * @param string $php_ext
+ * @param string $notification_types_table
+ * @param string $notifications_table
+ * @param string $user_notifications_table
+ * @return phpbb_notification_manager
+ */
+ public function __construct($notification_types, $notification_methods, $phpbb_container, phpbb_user_loader $user_loader, phpbb_db_driver $db, $user, $phpbb_root_path, $php_ext, $notification_types_table, $notifications_table, $user_notifications_table)
+ {
+ $this->notification_types = $notification_types;
+ $this->notification_methods = $notification_methods;
+ $this->phpbb_container = $phpbb_container;
+
+ $this->user_loader = $user_loader;
+ $this->db = $db;
+ $this->user = $user;
+
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+
+ $this->notification_types_table = $notification_types_table;
+ $this->notifications_table = $notifications_table;
+ $this->user_notifications_table = $user_notifications_table;
+ }
+
+ /**
+ * Load the user's notifications
+ *
+ * @param array $options Optional options to control what notifications are loaded
+ * notification_id Notification id to load (or array of notification ids)
+ * user_id User id to load notifications for (Default: $user->data['user_id'])
+ * order_by Order by (Default: notification_time)
+ * order_dir Order direction (Default: DESC)
+ * limit Number of notifications to load (Default: 5)
+ * start Notifications offset (Default: 0)
+ * all_unread Load all unread notifications? If set to true, count_unread is set to true (Default: false)
+ * count_unread Count all unread notifications? (Default: false)
+ * count_total Count all notifications? (Default: false)
+ * @return array Array of information based on the request with keys:
+ * 'notifications' array of notification type objects
+ * 'unread_count' number of unread notifications the user has if count_unread is true in the options
+ * 'total_count' number of notifications the user has if count_total is true in the options
+ */
+ public function load_notifications(array $options = array())
+ {
+ // Merge default options
+ $options = array_merge(array(
+ 'notification_id' => false,
+ 'user_id' => $this->user->data['user_id'],
+ 'order_by' => 'notification_time',
+ 'order_dir' => 'DESC',
+ 'limit' => 0,
+ 'start' => 0,
+ 'all_unread' => false,
+ 'count_unread' => false,
+ 'count_total' => false,
+ ), $options);
+
+ // If all_unread, count_unread must be true
+ $options['count_unread'] = ($options['all_unread']) ? true : $options['count_unread'];
+
+ // Anonymous users and bots never receive notifications
+ if ($options['user_id'] == $this->user->data['user_id'] && ($this->user->data['user_id'] == ANONYMOUS || $this->user->data['user_type'] == USER_IGNORE))
+ {
+ return array(
+ 'notifications' => array(),
+ 'unread_count' => 0,
+ 'total_count' => 0,
+ );
+ }
+
+ $notifications = $user_ids = array();
+ $load_special = array();
+ $total_count = $unread_count = 0;
+
+ if ($options['count_unread'])
+ {
+ // Get the total number of unread notifications
+ $sql = 'SELECT COUNT(n.notification_id) AS unread_count
+ FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt
+ WHERE n.user_id = ' . (int) $options['user_id'] . '
+ AND n.notification_read = 0
+ AND nt.notification_type = n.item_type
+ AND nt.notification_type_enabled = 1';
+ $result = $this->db->sql_query($sql);
+ $unread_count = (int) $this->db->sql_fetchfield('unread_count', $result);
+ $this->db->sql_freeresult($result);
+ }
+
+ if ($options['count_total'])
+ {
+ // Get the total number of notifications
+ $sql = 'SELECT COUNT(n.notification_id) AS total_count
+ FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt
+ WHERE n.user_id = ' . (int) $options['user_id'] . '
+ AND nt.notification_type = n.item_type
+ AND nt.notification_type_enabled = 1';
+ $result = $this->db->sql_query($sql);
+ $total_count = (int) $this->db->sql_fetchfield('total_count', $result);
+ $this->db->sql_freeresult($result);
+ }
+
+ if (!$options['count_total'] || $total_count)
+ {
+ $rowset = array();
+
+ // Get the main notifications
+ $sql = 'SELECT n.*
+ FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt
+ WHERE n.user_id = ' . (int) $options['user_id'] .
+ (($options['notification_id']) ? ((is_array($options['notification_id'])) ? ' AND ' . $this->db->sql_in_set('n.notification_id', $options['notification_id']) : ' AND n.notification_id = ' . (int) $options['notification_id']) : '') . '
+ AND nt.notification_type = n.item_type
+ AND nt.notification_type_enabled = 1
+ ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']);
+ $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $rowset[$row['notification_id']] = $row;
+ }
+ $this->db->sql_freeresult($result);
+
+ // Get all unread notifications
+ if ($unread_count && $options['all_unread'] && !empty($rowset))
+ {
+ $sql = 'SELECT n.*
+ FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt
+ WHERE n.user_id = ' . (int) $options['user_id'] . '
+ AND n.notification_read = 0
+ AND ' . $this->db->sql_in_set('n.notification_id', array_keys($rowset), true) . '
+ AND nt.notification_type = n.item_type
+ AND nt.notification_type_enabled = 1
+ ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']);
+ $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $rowset[$row['notification_id']] = $row;
+ }
+ $this->db->sql_freeresult($result);
+ }
+
+ foreach ($rowset as $row)
+ {
+ $notification = $this->get_item_type_class($row['item_type'], $row);
+
+ // Array of user_ids to query all at once
+ $user_ids = array_merge($user_ids, $notification->users_to_query());
+
+ // Some notification types also require querying additional tables themselves
+ if (!isset($load_special[$row['item_type']]))
+ {
+ $load_special[$row['item_type']] = array();
+ }
+ $load_special[$row['item_type']] = array_merge($load_special[$row['item_type']], $notification->get_load_special());
+
+ $notifications[$row['notification_id']] = $notification;
+ }
+
+ $this->user_loader->load_users($user_ids);
+
+ // Allow each type to load its own special items
+ foreach ($load_special as $item_type => $data)
+ {
+ $item_class = $this->get_item_type_class($item_type);
+
+ $item_class->load_special($data, $notifications);
+ }
+ }
+
+ return array(
+ 'notifications' => $notifications,
+ 'unread_count' => $unread_count,
+ 'total_count' => $total_count,
+ );
+ }
+
+ /**
+ * Mark notifications read
+ *
+ * @param bool|string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types
+ * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids
+ * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids
+ * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False)
+ */
+ public function mark_notifications_read($item_type, $item_id, $user_id, $time = false)
+ {
+ $time = ($time !== false) ? $time : time();
+
+ $sql = 'UPDATE ' . $this->notifications_table . "
+ SET notification_read = 1
+ WHERE notification_time <= " . (int) $time .
+ (($item_type !== false) ? ' AND ' . (is_array($item_type) ? $this->db->sql_in_set('item_type', $item_type) : " item_type = '" . $this->db->sql_escape($item_type) . "'") : '') .
+ (($item_id !== false) ? ' AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) : '');
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Mark notifications read from a parent identifier
+ *
+ * @param string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types)
+ * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids
+ * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids
+ * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False)
+ */
+ public function mark_notifications_read_by_parent($item_type, $item_parent_id, $user_id, $time = false)
+ {
+ if (is_array($item_type))
+ {
+ foreach ($item_type as $type)
+ {
+ $this->mark_notifications_read_by_parent($type, $item_parent_id, $user_id, $time);
+ }
+
+ return;
+ }
+
+ $time = ($time !== false) ? $time : time();
+
+ $sql = 'UPDATE ' . $this->notifications_table . "
+ SET notification_read = 1
+ WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
+ AND notification_time <= " . (int) $time .
+ (($item_parent_id !== false) ? ' AND ' . (is_array($item_parent_id) ? $this->db->sql_in_set('item_parent_id', $item_parent_id) : 'item_parent_id = ' . (int) $item_parent_id) : '') .
+ (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : '');
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Mark notifications read
+ *
+ * @param int|array $notification_id Notification id or array of notification ids.
+ * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False)
+ */
+ public function mark_notifications_read_by_id($notification_id, $time = false)
+ {
+ $time = ($time !== false) ? $time : time();
+
+ $sql = 'UPDATE ' . $this->notifications_table . "
+ SET notification_read = 1
+ WHERE notification_time <= " . (int) $time . '
+ AND ' . ((is_array($notification_id)) ? $this->db->sql_in_set('notification_id', $notification_id) : 'notification_id = ' . (int) $notification_id);
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Add a notification
+ *
+ * @param string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types)
+ * Note: If you send an array of types, any user who could receive multiple notifications from this single item will only receive
+ * a single notification. If they MUST receive multiple notifications, call this function multiple times instead of sending an array
+ * @param array $data Data specific for this type that will be inserted
+ * @param array $options Optional options to control what notifications are loaded
+ * ignore_users array of data to specify which users should not receive certain types of notifications
+ * @return array Information about what users were notified and how they were notified
+ */
+ public function add_notifications($item_type, $data, array $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ if (is_array($item_type))
+ {
+ $notified_users = array();
+ $temp_options = $options;
+
+ foreach ($item_type as $type)
+ {
+ $temp_options['ignore_users'] = $options['ignore_users'] + $notified_users;
+ $notified_users += $this->add_notifications($type, $data, $temp_options);
+ }
+
+ return $notified_users;
+ }
+
+ $item_id = $this->get_item_type_class($item_type)->get_item_id($data);
+
+ // find out which users want to receive this type of notification
+ $notify_users = $this->get_item_type_class($item_type)->find_users_for_notification($data, $options);
+
+ $this->add_notifications_for_users($item_type, $data, $notify_users);
+
+ return $notify_users;
+ }
+
+ /**
+ * Add a notification for specific users
+ *
+ * @param string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types)
+ * @param array $data Data specific for this type that will be inserted
+ * @param array $notify_users User list to notify
+ */
+ public function add_notifications_for_users($item_type, $data, $notify_users)
+ {
+ if (is_array($item_type))
+ {
+ foreach ($item_type as $type)
+ {
+ $this->add_notifications_for_users($type, $data, $notify_users);
+ }
+
+ return;
+ }
+
+ $sql = 'SELECT notification_type
+ FROM ' . $this->notification_types_table . "
+ WHERE notification_type = '" . $this->db->sql_escape($item_type) . "'";
+ $result = $this->db->sql_query($sql);
+
+ if ($this->db->sql_fetchrow($result) === false)
+ {
+ // Does not exist in the database, must add the item type
+ $sql = 'INSERT INTO ' . $this->notification_types_table . ' ' . $this->db->sql_build_array('INSERT', array(
+ 'notification_type' => $item_type,
+ 'notification_type_enabled' => 1,
+ ));
+ $this->db->sql_query($sql);
+ }
+
+ $this->db->sql_freeresult($result);
+
+ $item_id = $this->get_item_type_class($item_type)->get_item_id($data);
+
+ $user_ids = array();
+ $notification_objects = $notification_methods = array();
+ $new_rows = array();
+
+ // Never send notifications to the anonymous user!
+ unset($notify_users[ANONYMOUS]);
+
+ // Make sure not to send new notifications to users who've already been notified about this item
+ // This may happen when an item was added, but now new users are able to see the item
+ $sql = 'SELECT n.user_id
+ FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt
+ WHERE n.item_type = '" . $this->db->sql_escape($item_type) . "'
+ AND n.item_id = " . (int) $item_id . '
+ AND nt.notification_type = n.item_type
+ AND nt.notification_type_enabled = 1';
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ unset($notify_users[$row['user_id']]);
+ }
+ $this->db->sql_freeresult($result);
+
+ if (!sizeof($notify_users))
+ {
+ return;
+ }
+
+ // Allow notifications to perform actions before creating the insert array (such as run a query to cache some data needed for all notifications)
+ $notification = $this->get_item_type_class($item_type);
+ $pre_create_data = $notification->pre_create_insert_array($data, $notify_users);
+ unset($notification);
+
+ // Go through each user so we can insert a row in the DB and then notify them by their desired means
+ foreach ($notify_users as $user => $methods)
+ {
+ $notification = $this->get_item_type_class($item_type);
+
+ $notification->user_id = (int) $user;
+
+ // Store the creation array in our new rows that will be inserted later
+ $new_rows[] = $notification->create_insert_array($data, $pre_create_data);
+
+ // Users are needed to send notifications
+ $user_ids = array_merge($user_ids, $notification->users_to_query());
+
+ foreach ($methods as $method)
+ {
+ // setup the notification methods and add the notification to the queue
+ if ($method) // blank means we just insert it as a notification, but do not notify them by any other means
+ {
+ if (!isset($notification_methods[$method]))
+ {
+ $notification_methods[$method] = $this->get_method_class($method);
+ }
+
+ $notification_methods[$method]->add_to_queue($notification);
+ }
+ }
+ }
+
+ // insert into the db
+ $this->db->sql_multi_insert($this->notifications_table, $new_rows);
+
+ // We need to load all of the users to send notifications
+ $this->user_loader->load_users($user_ids);
+
+ // run the queue for each method to send notifications
+ foreach ($notification_methods as $method)
+ {
+ $method->notify();
+ }
+ }
+
+ /**
+ * Update a notification
+ *
+ * @param string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types)
+ * @param array $data Data specific for this type that will be updated
+ */
+ public function update_notifications($item_type, $data)
+ {
+ if (is_array($item_type))
+ {
+ foreach ($item_type as $type)
+ {
+ $this->update_notifications($type, $data);
+ }
+
+ return;
+ }
+
+ $notification = $this->get_item_type_class($item_type);
+
+ // Allow the notifications class to over-ride the update_notifications functionality
+ if (method_exists($notification, 'update_notifications'))
+ {
+ // Return False to over-ride the rest of the update
+ if ($notification->update_notifications($data) === false)
+ {
+ return;
+ }
+ }
+
+ $item_id = $notification->get_item_id($data);
+ $update_array = $notification->create_update_array($data);
+
+ $sql = 'UPDATE ' . $this->notifications_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $update_array) . "
+ WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
+ AND item_id = " . (int) $item_id;
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Delete a notification
+ *
+ * @param string|array $item_type Type identifier or array of item types (only acceptable if the $item_id is identical for the specified types)
+ * @param int|array $item_id Identifier within the type (or array of ids)
+ * @param array $data Data specific for this type that will be updated
+ */
+ public function delete_notifications($item_type, $item_id)
+ {
+ if (is_array($item_type))
+ {
+ foreach ($item_type as $type)
+ {
+ $this->delete_notifications($type, $item_id);
+ }
+
+ return;
+ }
+
+ $sql = 'DELETE FROM ' . $this->notifications_table . "
+ WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
+ AND " . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id);
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Get all of the subscription types
+ *
+ * @return array Array of item types
+ */
+ public function get_subscription_types()
+ {
+ $subscription_types = array();
+
+ foreach ($this->notification_types as $type_name => $data)
+ {
+ $type = $this->get_item_type_class($type_name);
+
+ if ($type instanceof phpbb_notification_type_interface && $type->is_available())
+ {
+ $options = array_merge(array(
+ 'id' => $type->get_type(),
+ 'lang' => 'NOTIFICATION_TYPE_' . strtoupper($type->get_type()),
+ 'group' => 'NOTIFICATION_GROUP_MISCELLANEOUS',
+ ), (($type::$notification_option !== false) ? $type::$notification_option : array()));
+
+ $subscription_types[$options['group']][$options['id']] = $options;
+ }
+ }
+
+ // Move Miscellaneous to the very last section
+ if (isset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']))
+ {
+ $miscellaneous = $subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'];
+ unset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']);
+ $subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'] = $miscellaneous;
+ }
+
+ return $subscription_types;
+ }
+
+ /**
+ * Get all of the subscription methods
+ *
+ * @return array Array of methods
+ */
+ public function get_subscription_methods()
+ {
+ $subscription_methods = array();
+
+ foreach ($this->notification_methods as $method_name => $data)
+ {
+ $method = $this->get_method_class($method_name);
+
+ if ($method instanceof phpbb_notification_method_interface && $method->is_available())
+ {
+ $subscription_methods[$method_name] = array(
+ 'id' => $method->get_type(),
+ 'lang' => 'NOTIFICATION_METHOD_' . strtoupper($method->get_type()),
+ );
+ }
+ }
+
+ return $subscription_methods;
+ }
+
+ /**
+ * Get global subscriptions (item_id = 0)
+ *
+ * @param bool|int $user_id The user_id to add the subscription for (bool false for current user)
+ *
+ * @return array Subscriptions
+ */
+ public function get_global_subscriptions($user_id = false)
+ {
+ $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id;
+
+ $subscriptions = array();
+
+ foreach ($this->get_subscription_types() as $group_name => $types)
+ {
+ foreach ($types as $id => $type)
+ {
+ $sql = 'SELECT method, notify
+ FROM ' . $this->user_notifications_table . '
+ WHERE user_id = ' . (int) $user_id . "
+ AND item_type = '" . $this->db->sql_escape($id) . "'
+ AND item_id = 0";
+ $result = $this->db->sql_query($sql);
+
+ $row = $this->db->sql_fetchrow($result);
+ if (!$row)
+ {
+ // No rows at all, default to ''
+ $subscriptions[$id] = array('');
+ }
+ else
+ {
+ do
+ {
+ if (!$row['notify'])
+ {
+ continue;
+ }
+
+ if (!isset($subscriptions[$id]))
+ {
+ $subscriptions[$id] = array();
+ }
+
+ $subscriptions[$id][] = $row['method'];
+ }
+ while ($row = $this->db->sql_fetchrow($result));
+ }
+
+ $this->db->sql_freeresult($result);
+ }
+ }
+
+ return $subscriptions;
+ }
+
+ /**
+ * Add a subscription
+ *
+ * @param string $item_type Type identifier of the subscription
+ * @param int $item_id The id of the item
+ * @param string $method The method of the notification e.g. '', 'email', or 'jabber'
+ * @param bool|int $user_id The user_id to add the subscription for (bool false for current user)
+ */
+ public function add_subscription($item_type, $item_id = 0, $method = '', $user_id = false)
+ {
+ if ($method !== '')
+ {
+ $this->add_subscription($item_type, $item_type, '', $user_id);
+ }
+
+ $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id;
+
+ $sql = 'SELECT notify
+ FROM ' . $this->user_notifications_table . "
+ WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
+ AND item_id = " . (int) $item_id . '
+ AND user_id = ' .(int) $user_id . "
+ AND method = '" . $this->db->sql_escape($method) . "'";
+ $this->db->sql_query($sql);
+ $current = $this->db->sql_fetchfield('notify');
+ $this->db->sql_freeresult();
+
+ if ($current === false)
+ {
+ $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' .
+ $this->db->sql_build_array('INSERT', array(
+ 'item_type' => $item_type,
+ 'item_id' => (int) $item_id,
+ 'user_id' => (int) $user_id,
+ 'method' => $method,
+ 'notify' => 1,
+ ));
+ $this->db->sql_query($sql);
+ }
+ else if (!$current)
+ {
+ $sql = 'UPDATE ' . $this->user_notifications_table . "
+ SET notify = 1
+ WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
+ AND item_id = " . (int) $item_id . '
+ AND user_id = ' .(int) $user_id . "
+ AND method = '" . $this->db->sql_escape($method) . "'";
+ $this->db->sql_query($sql);
+ }
+ }
+
+ /**
+ * Delete a subscription
+ *
+ * @param string $item_type Type identifier of the subscription
+ * @param int $item_id The id of the item
+ * @param string $method The method of the notification e.g. '', 'email', or 'jabber'
+ * @param bool|int $user_id The user_id to add the subscription for (bool false for current user)
+ */
+ public function delete_subscription($item_type, $item_id = 0, $method = '', $user_id = false)
+ {
+ $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id;
+
+ // If no method, make sure that no other notification methods for this item are selected before deleting
+ if ($method === '')
+ {
+ $sql = 'SELECT COUNT(*) as num_notifications
+ FROM ' . $this->user_notifications_table . "
+ WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
+ AND item_id = " . (int) $item_id . '
+ AND user_id = ' .(int) $user_id . "
+ AND method <> ''
+ AND notify = 1";
+ $this->db->sql_query($sql);
+ $num_notifications = $this->db->sql_fetchfield('num_notifications');
+ $this->db->sql_freeresult();
+
+ if ($num_notifications)
+ {
+ return;
+ }
+ }
+
+ $sql = 'UPDATE ' . $this->user_notifications_table . "
+ SET notify = 0
+ WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
+ AND item_id = " . (int) $item_id . '
+ AND user_id = ' .(int) $user_id . "
+ AND method = '" . $this->db->sql_escape($method) . "'";
+ $this->db->sql_query($sql);
+
+ if (!$this->db->sql_affectedrows())
+ {
+ $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' .
+ $this->db->sql_build_array('INSERT', array(
+ 'item_type' => $item_type,
+ 'item_id' => (int) $item_id,
+ 'user_id' => (int) $user_id,
+ 'method' => $method,
+ 'notify' => 0,
+ ));
+ $this->db->sql_query($sql);
+ }
+ }
+
+ /**
+ * Disable all notifications of a certain type
+ *
+ * This should be called when an extension which has notification types
+ * is disabled so that all those notifications are hidden and do not
+ * cause errors
+ *
+ * @param string $item_type Type identifier of the subscription
+ */
+ public function disable_notifications($item_type)
+ {
+ $sql = 'UPDATE ' . $this->notification_types_table . "
+ SET notification_type_enabled = 0
+ WHERE notification_type = '" . $this->db->sql_escape($item_type) . "'";
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Purge all notifications of a certain type
+ *
+ * This should be called when an extension which has notification types
+ * is purged so that all those notifications are removed
+ *
+ * @param string $item_type Type identifier of the subscription
+ */
+ public function purge_notifications($item_type)
+ {
+ $sql = 'DELETE FROM ' . $this->notifications_table . "
+ WHERE item_type = '" . $this->db->sql_escape($item_type) . "'";
+ $this->db->sql_query($sql);
+
+ $sql = 'DELETE FROM ' . $this->notification_types_table . "
+ WHERE notification_type = '" . $this->db->sql_escape($item_type) . "'";
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Enable all notifications of a certain type
+ *
+ * This should be called when an extension which has notification types
+ * that was disabled is re-enabled so that all those notifications that
+ * were hidden are shown again
+ *
+ * @param string $item_type Type identifier of the subscription
+ */
+ public function enable_notifications($item_type)
+ {
+ $sql = 'UPDATE ' . $this->notification_types_table . "
+ SET notification_type_enabled = 1
+ WHERE notification_type = '" . $this->db->sql_escape($item_type) . "'";
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Delete all notifications older than a certain time
+ *
+ * @param int $timestamp Unix timestamp to delete all notifications that were created before
+ */
+ public function prune_notifications($timestamp)
+ {
+ $sql = 'DELETE FROM ' . $this->notifications_table . '
+ WHERE notification_time < ' . (int) $timestamp;
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Helper to get the notifications item type class and set it up
+ */
+ public function get_item_type_class($item_type, $data = array())
+ {
+ $item_type = (strpos($item_type, 'notification.type.') === 0) ? $item_type : 'notification.type.' . $item_type;
+
+ $item = $this->load_object($item_type);
+
+ $item->set_initial_data($data);
+
+ return $item;
+ }
+
+ /**
+ * Helper to get the notifications method class and set it up
+ */
+ public function get_method_class($method_name)
+ {
+ $method_name = (strpos($method_name, 'notification.method.') === 0) ? $method_name : 'notification.method.' . $method_name;
+
+ return $this->load_object($method_name);
+ }
+
+ /**
+ * Helper to load objects (notification types/methods)
+ */
+ protected function load_object($object_name)
+ {
+ $object = $this->phpbb_container->get($object_name);
+
+ if (method_exists($object, 'set_notification_manager'))
+ {
+ $object->set_notification_manager($this);
+ }
+
+ return $object;
+ }
+}
diff --git a/phpBB/includes/notification/method/base.php b/phpBB/includes/notification/method/base.php
new file mode 100644
index 0000000000..22418c9be8
--- /dev/null
+++ b/phpBB/includes/notification/method/base.php
@@ -0,0 +1,116 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Base notifications method class
+* @package notifications
+*/
+abstract class phpbb_notification_method_base implements phpbb_notification_method_interface
+{
+ /** @var phpbb_notification_manager */
+ protected $notification_manager;
+
+ /** @var phpbb_user_loader */
+ protected $user_loader;
+
+ /** @var phpbb_db_driver */
+ protected $db;
+
+ /** @var phpbb_cache_service */
+ protected $cache;
+
+ /** @var phpbb_template */
+ protected $template;
+
+ /** @var phpbb_extension_manager */
+ protected $extension_manager;
+
+ /** @var phpbb_user */
+ protected $user;
+
+ /** @var phpbb_auth */
+ protected $auth;
+
+ /** @var phpbb_config */
+ protected $config;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /**
+ * Queue of messages to be sent
+ *
+ * @var array
+ */
+ protected $queue = array();
+
+ /**
+ * Notification Method Base Constructor
+ *
+ * @param phpbb_user_loader $user_loader
+ * @param phpbb_db_driver $db
+ * @param phpbb_cache_driver_interface $cache
+ * @param phpbb_user $user
+ * @param phpbb_auth $auth
+ * @param phpbb_config $config
+ * @param string $phpbb_root_path
+ * @param string $php_ext
+ * @return phpbb_notification_method_base
+ */
+ public function __construct(phpbb_user_loader $user_loader, phpbb_db_driver $db, phpbb_cache_driver_interface $cache, $user, phpbb_auth $auth, phpbb_config $config, $phpbb_root_path, $php_ext)
+ {
+ $this->user_loader = $user_loader;
+ $this->db = $db;
+ $this->cache = $cache;
+ $this->user = $user;
+ $this->auth = $auth;
+ $this->config = $config;
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+ }
+
+ /**
+ * Set notification manager (required)
+ *
+ * @param phpbb_notification_manager $notification_manager
+ */
+ public function set_notification_manager(phpbb_notification_manager $notification_manager)
+ {
+ $this->notification_manager = $notification_manager;
+ }
+
+ /**
+ * Add a notification to the queue
+ *
+ * @param phpbb_notification_type_interface $notification
+ */
+ public function add_to_queue(phpbb_notification_type_interface $notification)
+ {
+ $this->queue[] = $notification;
+ }
+
+ /**
+ * Empty the queue
+ */
+ protected function empty_queue()
+ {
+ $this->queue = array();
+ }
+}
diff --git a/phpBB/includes/notification/method/email.php b/phpBB/includes/notification/method/email.php
new file mode 100644
index 0000000000..429dfda2ba
--- /dev/null
+++ b/phpBB/includes/notification/method/email.php
@@ -0,0 +1,129 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Email notification method class
+* This class handles sending emails for notifications
+*
+* @package notifications
+*/
+class phpbb_notification_method_email extends phpbb_notification_method_base
+{
+ /**
+ * Get notification method name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'email';
+ }
+
+ /**
+ * Notify method (since jabber gets sent through the same messenger, we let the jabber class inherit from this to reduce code duplication)
+ *
+ * @var mixed
+ */
+ protected $notify_method = NOTIFY_EMAIL;
+
+ /**
+ * Base directory to prepend to the email template name
+ *
+ * @var string
+ */
+ protected $email_template_base_dir = '';
+
+ /**
+ * Is this method available for the user?
+ * This is checked on the notifications options
+ */
+ public function is_available()
+ {
+ // Email is always available
+ return true;
+ }
+
+ /**
+ * Parse the queue and notify the users
+ */
+ public function notify()
+ {
+ if (!sizeof($this->queue))
+ {
+ return;
+ }
+
+ // Load all users we want to notify (we need their email address)
+ $user_ids = $users = array();
+ foreach ($this->queue as $notification)
+ {
+ $user_ids[] = $notification->user_id;
+ }
+
+ // We do not send emails to banned users
+ if (!function_exists('phpbb_get_banned_user_ids'))
+ {
+ include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext);
+ }
+ $banned_users = phpbb_get_banned_user_ids($user_ids);
+
+ // Load all the users we need
+ $this->user_loader->load_users($user_ids);
+
+ // Load the messenger
+ if (!class_exists('messenger'))
+ {
+ include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext);
+ }
+ $messenger = new messenger();
+ $board_url = generate_board_url();
+
+ // Time to go through the queue and send emails
+ foreach ($this->queue as $notification)
+ {
+ if ($notification->get_email_template() === false)
+ {
+ continue;
+ }
+
+ $user = $this->user_loader->get_user($notification->user_id);
+
+ if ($user['user_type'] == USER_IGNORE || in_array($notification->user_id, $banned_users))
+ {
+ continue;
+ }
+
+ $messenger->template($this->email_template_base_dir . $notification->get_email_template(), $user['user_lang']);
+
+ $messenger->to($user['user_email'], $user['username']);
+
+ $messenger->assign_vars(array_merge(array(
+ 'USERNAME' => $user['username'],
+
+ 'U_NOTIFICATION_SETTINGS' => generate_board_url() . '/ucp.' . $this->php_ext . '?i=ucp_notifications',
+ ), $notification->get_email_template_variables()));
+
+ $messenger->send($this->notify_method);
+ }
+
+ // Save the queue in the messenger class (has to be called or these emails could be lost?)
+ $messenger->save_queue();
+
+ // We're done, empty the queue
+ $this->empty_queue();
+ }
+}
diff --git a/phpBB/includes/notification/method/interface.php b/phpBB/includes/notification/method/interface.php
new file mode 100644
index 0000000000..ef875942cc
--- /dev/null
+++ b/phpBB/includes/notification/method/interface.php
@@ -0,0 +1,48 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Base notifications method interface
+* @package notifications
+*/
+interface phpbb_notification_method_interface
+{
+ /**
+ * Get notification method name
+ *
+ * @return string
+ */
+ public function get_type();
+
+ /**
+ * Is this method available for the user?
+ * This is checked on the notifications options
+ */
+ public function is_available();
+
+ /**
+ * Add a notification to the queue
+ *
+ * @param phpbb_notification_type_interface $notification
+ */
+ public function add_to_queue(phpbb_notification_type_interface $notification);
+
+ /**
+ * Parse the queue and notify the users
+ */
+ public function notify();
+}
diff --git a/phpBB/includes/notification/method/jabber.php b/phpBB/includes/notification/method/jabber.php
new file mode 100644
index 0000000000..e3eb571fbc
--- /dev/null
+++ b/phpBB/includes/notification/method/jabber.php
@@ -0,0 +1,77 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Jabber notification method class
+* This class handles sending Jabber messages for notifications
+*
+* @package notifications
+*/
+class phpbb_notification_method_jabber extends phpbb_notification_method_email
+{
+ /**
+ * Get notification method name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'jabber';
+ }
+
+ /**
+ * Notify method (since jabber gets sent through the same messenger, we let the jabber class inherit from this to reduce code duplication)
+ *
+ * @var mixed
+ */
+ protected $notify_method = NOTIFY_IM;
+
+ /**
+ * Base directory to prepend to the email template name
+ *
+ * @var string
+ */
+ protected $email_template_base_dir = 'short/';
+
+ /**
+ * Is this method available for the user?
+ * This is checked on the notifications options
+ */
+ public function is_available()
+ {
+ return ($this->global_available() && $this->user->data['jabber']);
+ }
+
+ /**
+ * Is this method available at all?
+ * This is checked before notifications are sent
+ */
+ public function global_available()
+ {
+ return ($this->config['jab_enable'] && @extension_loaded('xml'));
+ }
+
+ public function notify()
+ {
+ if (!$this->global_available())
+ {
+ return;
+ }
+
+ return parent::notify();
+ }
+}
diff --git a/phpBB/includes/notification/type/approve_post.php b/phpBB/includes/notification/type/approve_post.php
new file mode 100644
index 0000000000..1a30781c35
--- /dev/null
+++ b/phpBB/includes/notification/type/approve_post.php
@@ -0,0 +1,140 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Post approved notifications class
+* This class handles notifications for posts when they are approved (to their authors)
+*
+* @package notifications
+*/
+class phpbb_notification_type_approve_post extends phpbb_notification_type_post
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'approve_post';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_POST_APPROVED';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'id' => 'moderation_queue',
+ 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE',
+ 'group' => 'NOTIFICATION_GROUP_POSTING',
+ );
+
+ /**
+ * Is available
+ */
+ public function is_available()
+ {
+ return !$this->auth->acl_get('m_approve');
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $post Data from
+ *
+ * @return array
+ */
+ public function find_users_for_notification($post, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ $users = array();
+ $users[$post['poster_id']] = array('');
+
+ $auth_read = $this->auth->acl_get_list(array_keys($users), 'f_read', $post['forum_id']);
+
+ if (empty($auth_read))
+ {
+ return array();
+ }
+
+ return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array(
+ 'item_type' => self::$notification_option['id'],
+ )));
+ }
+
+ /**
+ * Pre create insert array function
+ * This allows you to perform certain actions, like run a query
+ * and load data, before create_insert_array() is run. The data
+ * returned from this function will be sent to create_insert_array().
+ *
+ * @param array $post Post data from submit_post
+ * @param array $notify_users Notify users list
+ * Formated from find_users_for_notification()
+ * @return array Whatever you want to send to create_insert_array().
+ */
+ public function pre_create_insert_array($post, $notify_users)
+ {
+ // In the parent class, this is used to check if the post is already
+ // read by a user and marks the notification read if it was marked read.
+ // Returning an empty array in effect, forces it to be marked as unread
+ // (and also saves a query)
+ return array();
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $this->set_data('post_subject', $post['post_subject']);
+
+ $data = parent::create_insert_array($post, $pre_create_data);
+
+ $this->notification_time = $data['notification_time'] = time();
+
+ return $data;
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'post_approved';
+ }
+}
diff --git a/phpBB/includes/notification/type/approve_topic.php b/phpBB/includes/notification/type/approve_topic.php
new file mode 100644
index 0000000000..e728e9ac30
--- /dev/null
+++ b/phpBB/includes/notification/type/approve_topic.php
@@ -0,0 +1,138 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Topic approved notifications class
+* This class handles notifications for topics when they are approved (for authors)
+*
+* @package notifications
+*/
+class phpbb_notification_type_approve_topic extends phpbb_notification_type_topic
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'approve_topic';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_TOPIC_APPROVED';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'id' => 'moderation_queue',
+ 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE',
+ 'group' => 'NOTIFICATION_GROUP_POSTING',
+ );
+
+ /**
+ * Is available
+ */
+ public function is_available()
+ {
+ return !$this->auth->acl_get('m_approve');
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $post Data from
+ *
+ * @return array
+ */
+ public function find_users_for_notification($post, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ $users = array();
+ $users[$post['poster_id']] = array('');
+
+ $auth_read = $this->auth->acl_get_list(array_keys($users), 'f_read', $post['forum_id']);
+
+ if (empty($auth_read))
+ {
+ return array();
+ }
+
+ return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array(
+ 'item_type' => self::$notification_option['id'],
+ )));
+ }
+
+ /**
+ * Pre create insert array function
+ * This allows you to perform certain actions, like run a query
+ * and load data, before create_insert_array() is run. The data
+ * returned from this function will be sent to create_insert_array().
+ *
+ * @param array $post Post data from submit_post
+ * @param array $notify_users Notify users list
+ * Formated from find_users_for_notification()
+ * @return array Whatever you want to send to create_insert_array().
+ */
+ public function pre_create_insert_array($post, $notify_users)
+ {
+ // In the parent class, this is used to check if the post is already
+ // read by a user and marks the notification read if it was marked read.
+ // Returning an empty array in effect, forces it to be marked as unread
+ // (and also saves a query)
+ return array();
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $data = parent::create_insert_array($post, $pre_create_data);
+
+ $this->notification_time = $data['notification_time'] = time();
+
+ return $data;
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'topic_approved';
+ }
+}
diff --git a/phpBB/includes/notification/type/base.php b/phpBB/includes/notification/type/base.php
new file mode 100644
index 0000000000..600ef7c965
--- /dev/null
+++ b/phpBB/includes/notification/type/base.php
@@ -0,0 +1,479 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Base notifications class
+* @package notifications
+*/
+abstract class phpbb_notification_type_base implements phpbb_notification_type_interface
+{
+ /** @var phpbb_notification_manager */
+ protected $notification_manager;
+
+ /** @var phpbb_user_loader */
+ protected $user_loader;
+
+ /** @var phpbb_db_driver */
+ protected $db;
+
+ /** @var phpbb_cache_service */
+ protected $cache;
+
+ /** @var phpbb_template */
+ protected $template;
+
+ /** @var phpbb_user */
+ protected $user;
+
+ /** @var phpbb_auth */
+ protected $auth;
+
+ /** @var phpbb_config */
+ protected $config;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /** @var string */
+ protected $notification_types_table;
+
+ /** @var string */
+ protected $notifications_table;
+
+ /** @var string */
+ protected $user_notifications_table;
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use its default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = false;
+
+ /**
+ * Indentification data
+ * item_type - Type of the item (translates to the notification type)
+ * item_id - ID of the item (e.g. post_id, msg_id)
+ * item_parent_id - Parent item id (ex: for topic => forum_id, for post => topic_id, etc)
+ * user_id
+ * notification_read
+ * notification_time
+ * notification_data (special serialized field that each notification type can use to store stuff)
+ *
+ * @var array $data Notification row from the database
+ * This must be private, all interaction should use __get(), __set(), get_data(), set_data()
+ */
+ private $data = array();
+
+ /**
+ * Notification Type Base Constructor
+ *
+ * @param phpbb_user_loader $user_loader
+ * @param phpbb_db_driver $db
+ * @param phpbb_cache_driver_interface $cache
+ * @param phpbb_user $user
+ * @param phpbb_auth $auth
+ * @param phpbb_config $config
+ * @param string $phpbb_root_path
+ * @param string $php_ext
+ * @param string $notification_types_table
+ * @param string $notifications_table
+ * @param string $user_notifications_table
+ * @return phpbb_notification_type_base
+ */
+ public function __construct(phpbb_user_loader $user_loader, phpbb_db_driver $db, phpbb_cache_driver_interface $cache, $user, phpbb_auth $auth, phpbb_config $config, $phpbb_root_path, $php_ext, $notification_types_table, $notifications_table, $user_notifications_table)
+ {
+ $this->user_loader = $user_loader;
+ $this->db = $db;
+ $this->cache = $cache;
+ $this->user = $user;
+ $this->auth = $auth;
+ $this->config = $config;
+
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+
+ $this->notification_types_table = $notification_types_table;
+ $this->notifications_table = $notifications_table;
+ $this->user_notifications_table = $user_notifications_table;
+ }
+
+ /**
+ * Set notification manager (required)
+ *
+ * @param phpbb_notification_manager $notification_manager
+ */
+ public function set_notification_manager(phpbb_notification_manager $notification_manager)
+ {
+ $this->notification_manager = $notification_manager;
+ }
+
+ /**
+ * Set initial data from the database
+ *
+ * @param array $data Row directly from the database
+ */
+ public function set_initial_data($data = array())
+ {
+ // The row from the database (unless this is a new notification we're going to add)
+ $this->data = $data;
+ $this->data['notification_data'] = (isset($this->data['notification_data'])) ? unserialize($this->data['notification_data']) : array();
+ }
+
+ /**
+ * Magic method to get data from this notification
+ *
+ * @param mixed $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return (!isset($this->data[$name])) ? null : $this->data[$name];
+ }
+
+
+ /**
+ * Magic method to set data on this notification
+ *
+ * @param mixed $name
+ * @return null
+ */
+ public function __set($name, $value)
+ {
+ $this->data[$name] = $value;
+ }
+
+
+ /**
+ * Magic method to get a string of this notification
+ *
+ * Primarily for testing
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __toString()
+ {
+ return (!empty($this->data)) ? var_export($this->data, true) : $this->get_type();
+ }
+
+ /**
+ * Get special data (only important for the classes that extend this)
+ *
+ * @param string $name Name of the variable to get
+ * @return mixed
+ */
+ protected function get_data($name)
+ {
+ return ($name === false) ? $this->data['notification_data'] : ((isset($this->data['notification_data'][$name])) ? $this->data['notification_data'][$name] : null);
+ }
+
+ /**
+ * Set special data (only important for the classes that extend this)
+ *
+ * @param string $name Name of the variable to set
+ * @param mixed $value Value to set to the variable
+ * @return mixed
+ */
+ protected function set_data($name, $value)
+ {
+ $this->data['notification_data'][$name] = $value;
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $type_data Data unique to this notification type
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($type_data, $pre_create_data = array())
+ {
+ // Defaults
+ $this->data = array_merge(array(
+ 'item_id' => static::get_item_id($type_data),
+ 'item_type' => $this->get_type(),
+ 'item_parent_id' => static::get_item_parent_id($type_data),
+
+ 'notification_time' => time(),
+ 'notification_read' => false,
+
+ 'notification_data' => array(),
+ ), $this->data);
+
+ $data = $this->data;
+
+ $data['notification_data'] = serialize($data['notification_data']);
+
+ return $data;
+ }
+
+ /**
+ * Function for preparing the data for update in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $type_data Data unique to this notification type
+ * @return array Array of data ready to be updated in the database
+ */
+ public function create_update_array($type_data)
+ {
+ $data = $this->create_insert_array($type_data);
+
+ // Unset data unique to each row
+ unset(
+ $data['notification_time'], // Also unsetting time, since it always tries to change the time to current (if you actually need to change the time, over-ride this function)
+ $data['notification_id'],
+ $data['notification_read'],
+ $data['user_id']
+ );
+
+ return $data;
+ }
+
+ /**
+ * Mark this item read
+ *
+ * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
+ * @return string|null If $return is False, nothing will be returned, else the sql code to update this item
+ */
+ public function mark_read($return = false)
+ {
+ return $this->mark(false, $return);
+ }
+
+ /**
+ * Mark this item unread
+ *
+ * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
+ * @return string|null If $return is False, nothing will be returned, else the sql code to update this item
+ */
+ public function mark_unread($return = false)
+ {
+ return $this->mark(true, $return);
+ }
+
+ /**
+ * Prepare to output the notification to the template
+ *
+ * @return array Template variables
+ */
+ public function prepare_for_display()
+ {
+ if ($this->get_url())
+ {
+ $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id);
+ }
+ else
+ {
+ $redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : '');
+
+ $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&amp;redirect=' . urlencode($redirect));
+ }
+
+ return array(
+ 'NOTIFICATION_ID' => $this->notification_id,
+
+ 'AVATAR' => $this->get_avatar(),
+
+ 'FORMATTED_TITLE' => $this->get_title(),
+
+ 'URL' => $this->get_url(),
+ 'TIME' => $this->user->format_date($this->notification_time),
+
+ 'UNREAD' => !$this->notification_read,
+
+ 'U_MARK_READ' => (!$this->notification_read) ? $u_mark_read : '',
+ );
+ }
+
+ /**
+ * -------------- Fall back functions -------------------
+ */
+
+ /**
+ * URL to unsubscribe to this notification (fall back)
+ *
+ * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item
+ */
+ public function get_unsubscribe_url($method = false)
+ {
+ return false;
+ }
+
+ /**
+ * Get the user's avatar (fall back)
+ *
+ * @return string
+ */
+ public function get_avatar()
+ {
+ return '';
+ }
+
+ /**
+ * Get the special items to load (fall back)
+ *
+ * @return array
+ */
+ public function get_load_special()
+ {
+ return array();
+ }
+
+ /**
+ * Load the special items (fall back)
+ */
+ public function load_special($data, $notifications)
+ {
+ return;
+ }
+
+ /**
+ * Is available (fall back)
+ *
+ * @return bool
+ */
+ public function is_available()
+ {
+ return true;
+ }
+
+ /**
+ * Pre create insert array function (fall back)
+ *
+ * @return array
+ */
+ public function pre_create_insert_array($type_data, $notify_users)
+ {
+ return array();
+ }
+
+ /**
+ * -------------- Helper functions -------------------
+ */
+
+ /**
+ * Find the users who want to receive notifications (helper)
+ *
+ * @param array $user_ids User IDs to check if they want to receive notifications
+ * (Bool False to check all users besides anonymous and bots (USER_IGNORE))
+ *
+ * @return array
+ */
+ protected function check_user_notification_options($user_ids = false, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ 'item_type' => $this->get_type(),
+ 'item_id' => 0, // Global by default
+ ), $options);
+
+ if ($user_ids === false)
+ {
+ $user_ids = array();
+
+ $sql = 'SELECT user_id
+ FROM ' . USERS_TABLE . '
+ WHERE user_id <> ' . ANONYMOUS . '
+ AND user_type <> ' . USER_IGNORE;
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $user_ids[] = $row['user_id'];
+ }
+ $this->db->sql_freeresult($result);
+ }
+
+ if (empty($user_ids))
+ {
+ return array();
+ }
+
+ $rowset = $resulting_user_ids = array();
+
+ $sql = 'SELECT user_id, method, notify
+ FROM ' . $this->user_notifications_table . '
+ WHERE ' . $this->db->sql_in_set('user_id', $user_ids) . "
+ AND item_type = '" . $this->db->sql_escape($options['item_type']) . "'
+ AND item_id = " . (int) $options['item_id'];
+ $result = $this->db->sql_query($sql);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $resulting_user_ids[] = $row['user_id'];
+
+ if (!$row['notify'] || (isset($options['ignore_users'][$row['user_id']]) && in_array($row['method'], $options['ignore_users'][$row['user_id']])))
+ {
+ continue;
+ }
+
+ if (!isset($rowset[$row['user_id']]))
+ {
+ $rowset[$row['user_id']] = array();
+ }
+
+ $rowset[$row['user_id']][] = $row['method'];
+ }
+
+ $this->db->sql_freeresult($result);
+
+ foreach ($user_ids as $user_id)
+ {
+ if (!in_array($user_id, $resulting_user_ids) && !isset($options['ignore_users'][$user_id]))
+ {
+ // No rows at all for this user, default to ''
+ $rowset[$user_id] = array('');
+ }
+ }
+
+ return $rowset;
+ }
+
+ /**
+ * Mark this item read/unread helper
+ *
+ * @param bool $unread Unread (True/False) (Default: False)
+ * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
+ * @return string|null If $return is False, nothing will be returned, else the sql code to update this item
+ */
+ protected function mark($unread = true, $return = false)
+ {
+ $this->notification_read = (bool) !$unread;
+
+ $where = array(
+ "item_type = '" . $this->db->sql_escape($this->item_type) . "'",
+ 'item_id = ' . (int) $this->item_id,
+ 'user_id = ' . (int) $this->user_id,
+ );
+ $where = implode(' AND ', $where);
+
+ if ($return)
+ {
+ return $where;
+ }
+
+ $sql = 'UPDATE ' . $this->notifications_table . '
+ SET notification_read = ' . (int) $this->notification_read . '
+ WHERE ' . $where;
+ $this->db->sql_query($sql);
+ }
+}
diff --git a/phpBB/includes/notification/type/bookmark.php b/phpBB/includes/notification/type/bookmark.php
new file mode 100644
index 0000000000..4e48a967d0
--- /dev/null
+++ b/phpBB/includes/notification/type/bookmark.php
@@ -0,0 +1,137 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Bookmark updating notifications class
+* This class handles notifications for replies to a bookmarked topic
+*
+* @package notifications
+*/
+class phpbb_notification_type_bookmark extends phpbb_notification_type_post
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'bookmark';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_BOOKMARK';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'lang' => 'NOTIFICATION_TYPE_BOOKMARK',
+ 'group' => 'NOTIFICATION_GROUP_POSTING',
+ );
+
+ /**
+ * Is available
+ */
+ public function is_available()
+ {
+ return $this->config['allow_bookmarks'];
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $post Data from
+ *
+ * @return array
+ */
+ public function find_users_for_notification($post, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ $users = array();
+
+ $sql = 'SELECT user_id
+ FROM ' . BOOKMARKS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('topic_id', $post['topic_id']) . '
+ AND user_id <> ' . (int) $post['poster_id'];
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $users[] = $row['user_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (empty($users))
+ {
+ return array();
+ }
+
+ $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']);
+
+ if (empty($auth_read))
+ {
+ return array();
+ }
+
+ $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options);
+
+ // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications
+ $update_notifications = array();
+ $sql = 'SELECT n.*
+ FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt
+ WHERE n.item_type = '" . $this->get_type() . "'
+ AND n.item_parent_id = " . (int) self::get_item_parent_id($post) . '
+ AND n.notification_read = 0
+ AND nt.notification_type = n.item_type
+ AND nt.notification_type_enabled = 1';
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ // Do not create a new notification
+ unset($notify_users[$row['user_id']]);
+
+ $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row);
+ $sql = 'UPDATE ' . $this->notifications_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . '
+ WHERE notification_id = ' . $row['notification_id'];
+ $this->db->sql_query($sql);
+ }
+ $this->db->sql_freeresult($result);
+
+ return $notify_users;
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'bookmark';
+ }
+}
diff --git a/phpBB/includes/notification/type/disapprove_post.php b/phpBB/includes/notification/type/disapprove_post.php
new file mode 100644
index 0000000000..951c7e0254
--- /dev/null
+++ b/phpBB/includes/notification/type/disapprove_post.php
@@ -0,0 +1,120 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Post disapproved notifications class
+* This class handles notifications for posts when they are disapproved (for authors)
+*
+* @package notifications
+*/
+class phpbb_notification_type_disapprove_post extends phpbb_notification_type_approve_post
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'disapprove_post';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_POST_DISAPPROVED';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'id' => 'moderation_queue',
+ 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE',
+ 'group' => 'NOTIFICATION_GROUP_POSTING',
+ );
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title()
+ {
+ return $this->user->lang(
+ $this->language_key,
+ censor_text($this->get_data('topic_title')),
+ $this->get_data('disapprove_reason')
+ );
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return '';
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ return array_merge(parent::get_email_template_variables(), array(
+ 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')),
+ ));
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $this->set_data('disapprove_reason', $post['disapprove_reason']);
+
+ $data = parent::create_insert_array($post);
+
+ $this->notification_time = $data['notification_time'] = time();
+
+ return $data;
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'post_disapproved';
+ }
+}
diff --git a/phpBB/includes/notification/type/disapprove_topic.php b/phpBB/includes/notification/type/disapprove_topic.php
new file mode 100644
index 0000000000..038e528797
--- /dev/null
+++ b/phpBB/includes/notification/type/disapprove_topic.php
@@ -0,0 +1,120 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Topic disapproved notifications class
+* This class handles notifications for topics when they are disapproved (for authors)
+*
+* @package notifications
+*/
+class phpbb_notification_type_disapprove_topic extends phpbb_notification_type_approve_topic
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'disapprove_topic';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_TOPIC_DISAPPROVED';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'id' => 'moderation_queue',
+ 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE',
+ 'group' => 'NOTIFICATION_GROUP_POSTING',
+ );
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title()
+ {
+ return $this->user->lang(
+ $this->language_key,
+ censor_text($this->get_data('topic_title')),
+ $this->get_data('disapprove_reason')
+ );
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return '';
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ return array_merge(parent::get_email_template_variables(), array(
+ 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')),
+ ));
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $this->set_data('disapprove_reason', $post['disapprove_reason']);
+
+ $data = parent::create_insert_array($post, $pre_create_data);
+
+ $this->notification_time = $data['notification_time'] = time();
+
+ return $data;
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'topic_disapproved';
+ }
+}
diff --git a/phpBB/includes/notification/type/interface.php b/phpBB/includes/notification/type/interface.php
new file mode 100644
index 0000000000..a40fdafd09
--- /dev/null
+++ b/phpBB/includes/notification/type/interface.php
@@ -0,0 +1,189 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Base notifications interface
+* @package notifications
+*/
+interface phpbb_notification_type_interface
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type();
+
+ /**
+ * Set initial data from the database
+ *
+ * @param array $data Row directly from the database
+ */
+ public function set_initial_data($data);
+
+ /**
+ * Get the id of the item
+ *
+ * @param array $type_data The type specific data
+ */
+ public static function get_item_id($type_data);
+
+ /**
+ * Get the id of the parent
+ *
+ * @param array $type_data The type specific data
+ */
+ public static function get_item_parent_id($type_data);
+
+ /**
+ * Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options)
+ *
+ * @return bool True/False whether or not this is available to the user
+ */
+ public function is_available();
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $type_data The type specific data
+ * @param array $options Options for finding users for notification
+ * ignore_users => array of users and user types that should not receive notifications from this type because they've already been notified
+ * e.g.: array(2 => array(''), 3 => array('', 'email'), ...)
+ *
+ * @return array
+ */
+ public function find_users_for_notification($type_data, $options);
+
+ /**
+ * Users needed to query before this notification can be displayed
+ *
+ * @return array Array of user_ids
+ */
+ public function users_to_query();
+
+ /**
+ * Get the special items to load
+ *
+ * @return array Data will be combined sent to load_special() so you can run a single query and get data required for this notification type
+ */
+ public function get_load_special();
+
+ /**
+ * Load the special items
+ *
+ * @param array $data Data from get_load_special()
+ * @param array $notifications Array of notifications (key is notification_id, value is the notification objects)
+ */
+ public function load_special($data, $notifications);
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title();
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url();
+
+ /**
+ * URL to unsubscribe to this notification
+ *
+ * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item
+ */
+ public function get_unsubscribe_url($method);
+
+ /**
+ * Get the user's avatar (the user who caused the notification typically)
+ *
+ * @return string
+ */
+ public function get_avatar();
+
+ /**
+ * Prepare to output the notification to the template
+ */
+ public function prepare_for_display();
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template();
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables();
+
+ /**
+ * Pre create insert array function
+ * This allows you to perform certain actions, like run a query
+ * and load data, before create_insert_array() is run. The data
+ * returned from this function will be sent to create_insert_array().
+ *
+ * @param array $type_data The type specific data
+ * @param array $notify_users Notify users list
+ * Formated from find_users_for_notification()
+ * @return array Whatever you want to send to create_insert_array().
+ */
+ public function pre_create_insert_array($type_data, $notify_users);
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $type_data The type specific data
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($type_data, $pre_create_data);
+
+ /**
+ * Function for preparing the data for update in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $type_data Data unique to this notification type
+ *
+ * @return array Array of data ready to be updated in the database
+ */
+ public function create_update_array($type_data);
+
+ /**
+ * Mark this item read
+ *
+ * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
+ * @return string
+ */
+ public function mark_read($return);
+
+ /**
+ * Mark this item unread
+ *
+ * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
+ * @return string
+ */
+ public function mark_unread($return);
+}
diff --git a/phpBB/includes/notification/type/pm.php b/phpBB/includes/notification/type/pm.php
new file mode 100644
index 0000000000..b3db7ad5ad
--- /dev/null
+++ b/phpBB/includes/notification/type/pm.php
@@ -0,0 +1,184 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Private message notifications class
+* This class handles notifications for private messages
+*
+* @package notifications
+*/
+class phpbb_notification_type_pm extends phpbb_notification_type_base
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'pm';
+ }
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'lang' => 'NOTIFICATION_TYPE_PM',
+ );
+
+ /**
+ * Is available
+ */
+ public function is_available()
+ {
+ return ($this->config['allow_privmsg'] && $this->auth->acl_get('u_readpm'));
+ }
+
+ /**
+ * Get the id of the
+ *
+ * @param array $pm The data from the private message
+ */
+ public static function get_item_id($pm)
+ {
+ return (int) $pm['msg_id'];
+ }
+
+ /**
+ * Get the id of the parent
+ *
+ * @param array $pm The data from the pm
+ */
+ public static function get_item_parent_id($pm)
+ {
+ // No parent
+ return 0;
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $pm Data from
+ *
+ * @return array
+ */
+ public function find_users_for_notification($pm, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ if (!sizeof($pm['recipients']))
+ {
+ return array();
+ }
+
+ unset($pm['recipients'][$pm['from_user_id']]);
+
+ $this->user_loader->load_users(array_keys($pm['recipients']));
+
+ return $this->check_user_notification_options(array_keys($pm['recipients']), $options);
+ }
+
+ /**
+ * Get the user's avatar
+ */
+ public function get_avatar()
+ {
+ return $this->user_loader->get_avatar($this->get_data('from_user_id'));
+ }
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title()
+ {
+ $username = $this->user_loader->get_username($this->get_data('from_user_id'), 'no_profile');
+
+ return $this->user->lang('NOTIFICATION_PM', $username, $this->get_data('message_subject'));
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'privmsg_notify';
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ $user_data = $this->user_loader->get_user($this->get_data('from_user_id'));
+
+ return array(
+ 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']),
+ 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))),
+
+ 'U_VIEW_MESSAGE' => generate_board_url() . '/ucp.' . $this->php_ext . "?i=pm&mode=view&p={$this->item_id}",
+ );
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return append_sid($this->phpbb_root_path . 'ucp.' . $this->php_ext, "i=pm&amp;mode=view&amp;p={$this->item_id}");
+ }
+
+ /**
+ * Users needed to query before this notification can be displayed
+ *
+ * @return array Array of user_ids
+ */
+ public function users_to_query()
+ {
+ return array($this->get_data('from_user_id'));
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($pm, $pre_create_data = array())
+ {
+ $this->set_data('from_user_id', $pm['from_user_id']);
+
+ $this->set_data('message_subject', $pm['message_subject']);
+
+ return parent::create_insert_array($pm, $pre_create_data);
+ }
+}
diff --git a/phpBB/includes/notification/type/post.php b/phpBB/includes/notification/type/post.php
new file mode 100644
index 0000000000..ddfa720e5e
--- /dev/null
+++ b/phpBB/includes/notification/type/post.php
@@ -0,0 +1,370 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Post notifications class
+* This class handles notifications for replies to a topic
+*
+* @package notifications
+*/
+class phpbb_notification_type_post extends phpbb_notification_type_base
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'post';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_POST';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'lang' => 'NOTIFICATION_TYPE_POST',
+ 'group' => 'NOTIFICATION_GROUP_POSTING',
+ );
+
+ /**
+ * Is available
+ */
+ public function is_available()
+ {
+ return $this->config['allow_topic_notify'];
+ }
+
+ /**
+ * Get the id of the item
+ *
+ * @param array $post The data from the post
+ */
+ public static function get_item_id($post)
+ {
+ return (int) $post['post_id'];
+ }
+
+ /**
+ * Get the id of the parent
+ *
+ * @param array $post The data from the post
+ */
+ public static function get_item_parent_id($post)
+ {
+ return (int) $post['topic_id'];
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $post Data from
+ *
+ * @return array
+ */
+ public function find_users_for_notification($post, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ $users = array();
+
+ $sql = 'SELECT user_id
+ FROM ' . TOPICS_WATCH_TABLE . '
+ WHERE topic_id = ' . (int) $post['topic_id'] . '
+ AND notify_status = ' . NOTIFY_YES . '
+ AND user_id <> ' . (int) $post['poster_id'];
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $users[] = $row['user_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (empty($users))
+ {
+ return array();
+ }
+
+ $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']);
+
+ if (empty($auth_read))
+ {
+ return array();
+ }
+
+ $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options);
+
+ // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications
+ $update_notifications = array();
+ $sql = 'SELECT n.*
+ FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt
+ WHERE n.item_type = '" . $this->get_type() . "'
+ AND n.item_parent_id = " . (int) self::get_item_parent_id($post) . '
+ AND n.notification_read = 0
+ AND nt.notification_type = n.item_type
+ AND nt.notification_type_enabled = 1';
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ // Do not create a new notification
+ unset($notify_users[$row['user_id']]);
+
+ $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row);
+ $sql = 'UPDATE ' . $this->notifications_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . '
+ WHERE notification_id = ' . $row['notification_id'];
+ $this->db->sql_query($sql);
+ }
+ $this->db->sql_freeresult($result);
+
+ return $notify_users;
+ }
+
+ /**
+ * Get the user's avatar
+ */
+ public function get_avatar()
+ {
+ return $this->user_loader->get_avatar($this->get_data('poster_id'));
+ }
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title()
+ {
+ $responders = $this->get_data('responders');
+ $usernames = array();
+
+ if (!is_array($responders))
+ {
+ $responders = array();
+ }
+
+ $responders = array_merge(array(array(
+ 'poster_id' => $this->get_data('poster_id'),
+ 'username' => $this->get_data('post_username'),
+ )), $responders);
+
+ foreach ($responders as $responder)
+ {
+ if ($responder['username'])
+ {
+ $usernames[] = $responder['username'];
+ }
+ else
+ {
+ $usernames[] = $this->user_loader->get_username($responder['poster_id'], 'no_profile');
+ }
+ }
+
+ return $this->user->lang(
+ $this->language_key,
+ implode(', ', $usernames),
+ censor_text($this->get_data('topic_title'))
+ );
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'topic_notify';
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ if ($this->get_data('post_username'))
+ {
+ $username = $this->get_data('post_username');
+ }
+ else
+ {
+ $username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile');
+ }
+
+ return array(
+ 'AUTHOR_NAME' => htmlspecialchars_decode($username),
+ 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))),
+ 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))),
+
+ 'U_VIEW_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}",
+ 'U_NEWEST_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&view=unread#unread",
+ 'U_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}",
+ 'U_VIEW_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}",
+ 'U_FORUM' => generate_board_url() . "/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}",
+ 'U_STOP_WATCHING_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?uid={$this->user_id}&f={$this->get_data('forum_id')}&t={$this->item_parent_id}&unwatch=topic",
+ );
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "p={$this->item_id}#p{$this->item_id}");
+ }
+
+ /**
+ * Users needed to query before this notification can be displayed
+ *
+ * @return array Array of user_ids
+ */
+ public function users_to_query()
+ {
+ $responders = $this->get_data('responders');
+ $users = array(
+ $this->get_data('poster_id'),
+ );
+
+ if (is_array($responders))
+ {
+ foreach ($responders as $responder)
+ {
+ $users[] = $responder['poster_id'];
+ }
+ }
+
+ return $users;
+ }
+
+ /**
+ * Pre create insert array function
+ * This allows you to perform certain actions, like run a query
+ * and load data, before create_insert_array() is run. The data
+ * returned from this function will be sent to create_insert_array().
+ *
+ * @param array $post Post data from submit_post
+ * @param array $notify_users Notify users list
+ * Formated from find_users_for_notification()
+ * @return array Whatever you want to send to create_insert_array().
+ */
+ public function pre_create_insert_array($post, $notify_users)
+ {
+ if (!sizeof($notify_users))
+ {
+ return array();
+ }
+
+ $tracking_data = array();
+ $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . '
+ WHERE topic_id = ' . (int) $post['topic_id'] . '
+ AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users));
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $tracking_data[$row['user_id']] = $row['mark_time'];
+ }
+
+ return $tracking_data;
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $this->set_data('poster_id', $post['poster_id']);
+
+ $this->set_data('topic_title', $post['topic_title']);
+
+ $this->set_data('post_subject', $post['post_subject']);
+
+ $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''));
+
+ $this->set_data('forum_id', $post['forum_id']);
+
+ $this->set_data('forum_name', $post['forum_name']);
+
+ $this->notification_time = $post['post_time'];
+
+ // Topics can be "read" before they are public (while awaiting approval).
+ // Make sure that if the user has read the topic, it's marked as read in the notification
+ if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time)
+ {
+ $this->notification_read = true;
+ }
+
+ return parent::create_insert_array($post, $pre_create_data);
+ }
+
+ /**
+ * Add responders to the notification
+ *
+ * @param mixed $post
+ */
+ public function add_responders($post)
+ {
+ // Do not add them as a responder if they were the original poster that created the notification
+ if ($this->get_data('poster_id') == $post['poster_id'])
+ {
+ return array('notification_data' => serialize($this->get_data(false)));
+ }
+
+ $responders = $this->get_data('responders');
+
+ $responders = ($responders === null) ? array() : $responders;
+
+ foreach ($responders as $responder)
+ {
+ // Do not add them as a responder multiple times
+ if ($responder['poster_id'] == $post['poster_id'])
+ {
+ return array('notification_data' => serialize($this->get_data(false)));
+ }
+ }
+
+ $responders[] = array(
+ 'poster_id' => $post['poster_id'],
+ 'username' => (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''),
+ );
+
+ $this->set_data('responders', $responders);
+
+ return array('notification_data' => serialize($this->get_data(false)));
+ }
+}
diff --git a/phpBB/includes/notification/type/post_in_queue.php b/phpBB/includes/notification/type/post_in_queue.php
new file mode 100644
index 0000000000..1c29bee3cd
--- /dev/null
+++ b/phpBB/includes/notification/type/post_in_queue.php
@@ -0,0 +1,137 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Post in queue notifications class
+* This class handles notifications for posts that are put in the moderation queue (for moderators)
+*
+* @package notifications
+*/
+class phpbb_notification_type_post_in_queue extends phpbb_notification_type_post
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'post_in_queue';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_POST_IN_QUEUE';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'id' => 'needs_approval',
+ 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE',
+ 'group' => 'NOTIFICATION_GROUP_MODERATION',
+ );
+
+ /**
+ * Permission to check for (in find_users_for_notification)
+ *
+ * @var string Permission name
+ */
+ protected $permission = 'm_approve';
+
+ /**
+ * Is available
+ */
+ public function is_available()
+ {
+ $m_approve = $this->auth->acl_getf($this->permission, true);
+
+ return (!empty($m_approve));
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $post Data from the post
+ *
+ * @return array
+ */
+ public function find_users_for_notification($post, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ // 0 is for global
+ $auth_approve = $this->auth->acl_get_list(false, $this->permission, array($post['forum_id'], 0));
+
+ if (empty($auth_approve))
+ {
+ return array();
+ }
+
+ $auth_approve[$post['forum_id']] = array_unique(array_merge($auth_approve[$post['forum_id']], $auth_approve[0]));
+
+ return $this->check_user_notification_options($auth_approve[$post['forum_id']][$this->permission], array_merge($options, array(
+ 'item_type' => self::$notification_option['id'],
+ )));
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&amp;mode=approve_details&amp;f={$this->get_data('forum_id')}&amp;p={$this->item_id}");
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $data = parent::create_insert_array($post, $pre_create_data);
+
+ $this->notification_time = $data['notification_time'] = time();
+
+ return $data;
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'post_in_queue';
+ }
+}
diff --git a/phpBB/includes/notification/type/quote.php b/phpBB/includes/notification/type/quote.php
new file mode 100644
index 0000000000..5453b267c8
--- /dev/null
+++ b/phpBB/includes/notification/type/quote.php
@@ -0,0 +1,221 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Post quoting notifications class
+* This class handles notifications for quoting users in a post
+*
+* @package notifications
+*/
+class phpbb_notification_type_quote extends phpbb_notification_type_post
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'quote';
+ }
+
+ /**
+ * regular expression to match to find usernames
+ *
+ * @var string
+ */
+ protected static $regular_expression_match = '#\[quote=&quot;(.+?)&quot;#';
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_QUOTE';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'lang' => 'NOTIFICATION_TYPE_QUOTE',
+ 'group' => 'NOTIFICATION_GROUP_POSTING',
+ );
+
+ /**
+ * Is available
+ */
+ public function is_available()
+ {
+ return true;
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $post Data from
+ *
+ * @return array
+ */
+ public function find_users_for_notification($post, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ $usernames = false;
+ preg_match_all(self::$regular_expression_match, $post['post_text'], $usernames);
+
+ if (empty($usernames[1]))
+ {
+ return array();
+ }
+
+ $usernames[1] = array_unique($usernames[1]);
+
+ $usernames = array_map('utf8_clean_string', $usernames[1]);
+
+ $users = array();
+
+ $sql = 'SELECT user_id
+ FROM ' . USERS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('username_clean', $usernames) . '
+ AND user_id <> ' . (int) $post['poster_id'];
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $users[] = $row['user_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (empty($users))
+ {
+ return array();
+ }
+
+ $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']);
+
+ if (empty($auth_read))
+ {
+ return array();
+ }
+
+ $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options);
+
+ // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications
+ $update_notifications = array();
+ $sql = 'SELECT n.*
+ FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt
+ WHERE n.item_type = '" . $this->get_type() . "'
+ AND n.item_parent_id = " . (int) self::get_item_parent_id($post) . '
+ AND n.notification_read = 0
+ AND nt.notification_type = n.item_type
+ AND nt.notification_type_enabled = 1';
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ // Do not create a new notification
+ unset($notify_users[$row['user_id']]);
+
+ $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row);
+ $sql = 'UPDATE ' . $this->notifications_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . '
+ WHERE notification_id = ' . $row['notification_id'];
+ $this->db->sql_query($sql);
+ }
+ $this->db->sql_freeresult($result);
+
+ return $notify_users;
+ }
+
+ /**
+ * Update a notification
+ *
+ * @param array $data Data specific for this type that will be updated
+ */
+ public function update_notifications($post)
+ {
+ $old_notifications = array();
+ $sql = 'SELECT n.user_id
+ FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt
+ WHERE n.item_type = '" . $this->get_type() . "'
+ AND n.item_id = " . self::get_item_id($post) . '
+ AND nt.notification_type = n.item_type
+ AND nt.notification_type_enabled = 1';
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $old_notifications[] = $row['user_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ // Find the new users to notify
+ $notifications = $this->find_users_for_notification($post);
+
+ // Find the notifications we must delete
+ $remove_notifications = array_diff($old_notifications, array_keys($notifications));
+
+ // Find the notifications we must add
+ $add_notifications = array();
+ foreach (array_diff(array_keys($notifications), $old_notifications) as $user_id)
+ {
+ $add_notifications[$user_id] = $notifications[$user_id];
+ }
+
+ // Add the necessary notifications
+ $this->notification_manager->add_notifications_for_users($this->get_type(), $post, $add_notifications);
+
+ // Remove the necessary notifications
+ if (!empty($remove_notifications))
+ {
+ $sql = 'DELETE FROM ' . $this->notifications_table . "
+ WHERE item_type = '" . $this->get_type() . "'
+ AND item_id = " . self::get_item_id($post) . '
+ AND ' . $this->db->sql_in_set('user_id', $remove_notifications);
+ $this->db->sql_query($sql);
+ }
+
+ // return true to continue with the update code in the notifications service (this will update the rest of the notifications)
+ return true;
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'quote';
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ $user_data = $this->user_loader->get_user($this->get_data('poster_id'));
+
+ return array_merge(parent::get_email_template_variables(), array(
+ 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']),
+ ));
+ }
+}
diff --git a/phpBB/includes/notification/type/report_pm.php b/phpBB/includes/notification/type/report_pm.php
new file mode 100644
index 0000000000..3fa73bab41
--- /dev/null
+++ b/phpBB/includes/notification/type/report_pm.php
@@ -0,0 +1,229 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Private message reproted notifications class
+* This class handles notifications for private messages when they are reported
+*
+* @package notifications
+*/
+class phpbb_notification_type_report_pm extends phpbb_notification_type_pm
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'report_pm';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_REPORT_PM';
+
+ /**
+ * Permission to check for (in find_users_for_notification)
+ *
+ * @var string Permission name
+ */
+ protected $permission = 'm_report';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'id' => 'report',
+ 'lang' => 'NOTIFICATION_TYPE_REPORT',
+ 'group' => 'NOTIFICATION_GROUP_MODERATION',
+ );
+
+ /**
+ * Get the id of the parent
+ *
+ * @param array $pm The data from the pm
+ */
+ public static function get_item_parent_id($pm)
+ {
+ return (int) $pm['report_id'];
+ }
+
+ /**
+ * Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options)
+ *
+ * @return bool True/False whether or not this is available to the user
+ */
+ public function is_available()
+ {
+ $m_approve = $this->auth->acl_getf($this->permission, true);
+
+ return (!empty($m_approve));
+ }
+
+
+ /**
+ * Find the users who want to receive notifications
+ * (copied from post_in_queue)
+ *
+ * @param array $post Data from the post
+ *
+ * @return array
+ */
+ public function find_users_for_notification($post, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ // Global
+ $post['forum_id'] = 0;
+
+ $auth_approve = $this->auth->acl_get_list(false, $this->permission, $post['forum_id']);
+
+ if (empty($auth_approve))
+ {
+ return array();
+ }
+
+ if (($key = array_search($this->user->data['user_id'], $auth_approve[$post['forum_id']][$this->permission])))
+ {
+ unset($auth_approve[$post['forum_id']][$this->permission][$key]);
+ }
+
+ return $this->check_user_notification_options($auth_approve[$post['forum_id']][$this->permission], array_merge($options, array(
+ 'item_type' => self::$notification_option['id'],
+ )));
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'report_pm';
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ return array(
+ 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']),
+ 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))),
+
+ 'U_VIEW_REPORT' => generate_board_url() . "mcp.{$this->php_ext}?r={$this->item_parent_id}&amp;i=pm_reports&amp;mode=pm_report_details",
+ );
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "r={$this->item_parent_id}&amp;i=pm_reports&amp;mode=pm_report_details");
+ }
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title()
+ {
+ $this->user->add_lang('mcp');
+
+ $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile');
+
+ if ($this->get_data('report_text'))
+ {
+ return $this->user->lang(
+ $this->language_key,
+ $username,
+ censor_text($this->get_data('message_subject')),
+ $this->get_data('report_text')
+ );
+ }
+
+ if (isset($this->user->lang[$this->get_data('reason_title')]))
+ {
+ return $this->user->lang(
+ $this->language_key,
+ $username,
+ censor_text($this->get_data('message_subject')),
+ $this->user->lang[$this->get_data('reason_title')]
+ );
+ }
+
+ return $this->user->lang(
+ $this->language_key,
+ $username,
+ censor_text($this->get_data('message_subject')),
+ $this->get_data('reason_description')
+ );
+ }
+
+ /**
+ * Get the user's avatar
+ */
+ public function get_avatar()
+ {
+ return $this->user_loader->get_avatar($this->get_data('reporter_id'));
+ }
+
+ /**
+ * Users needed to query before this notification can be displayed
+ *
+ * @return array Array of user_ids
+ */
+ public function users_to_query()
+ {
+ return array($this->get_data('reporter_id'));
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $this->set_data('reporter_id', $this->user->data['user_id']);
+ $this->set_data('reason_title', strtoupper($post['reason_title']));
+ $this->set_data('reason_description', $post['reason_description']);
+ $this->set_data('report_text', $post['report_text']);
+
+ return parent::create_insert_array($post, $pre_create_data);
+ }
+}
diff --git a/phpBB/includes/notification/type/report_pm_closed.php b/phpBB/includes/notification/type/report_pm_closed.php
new file mode 100644
index 0000000000..63dfa92064
--- /dev/null
+++ b/phpBB/includes/notification/type/report_pm_closed.php
@@ -0,0 +1,155 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* PM report closed notifications class
+* This class handles notifications for when reports are closed on PMs (for the one who reported the PM)
+*
+* @package notifications
+*/
+class phpbb_notification_type_report_pm_closed extends phpbb_notification_type_pm
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'report_pm_closed';
+ }
+
+ /**
+ * Email template to use to send notifications
+ *
+ * @var string
+ */
+ public $email_template = '';
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_REPORT_CLOSED';
+
+ public function is_available()
+ {
+ return false;
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $pm Data from
+ *
+ * @return array
+ */
+ public function find_users_for_notification($pm, $options = array())
+ {
+ if ($pm['reporter'] == $this->user->data['user_id'])
+ {
+ return array();
+ }
+
+ return array($pm['reporter'] => array(''));
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return false;
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ return array();
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return '';
+ }
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title()
+ {
+ $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile');
+
+ return $this->user->lang(
+ $this->language_key,
+ $username,
+ censor_text($this->get_data('message_subject'))
+ );
+ }
+
+ /**
+ * Get the user's avatar
+ */
+ public function get_avatar()
+ {
+ return $this->get_user_avatar($this->get_data('closer_id'));
+ }
+
+ /**
+ * Users needed to query before this notification can be displayed
+ *
+ * @return array Array of user_ids
+ */
+ public function users_to_query()
+ {
+ return array($this->get_data('closer_id'));
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $pm PM Data
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($pm, $pre_create_data = array())
+ {
+ $this->set_data('closer_id', $pm['closer_id']);
+
+ $data = parent::create_insert_array($pm, $pre_create_data);
+
+ $this->notification_time = $data['notification_time'] = time();
+
+ return $data;
+ }
+}
diff --git a/phpBB/includes/notification/type/report_post.php b/phpBB/includes/notification/type/report_post.php
new file mode 100644
index 0000000000..de5c54a291
--- /dev/null
+++ b/phpBB/includes/notification/type/report_post.php
@@ -0,0 +1,196 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Reported post notifications class
+* This class handles notifications for reported posts
+*
+* @package notifications
+*/
+class phpbb_notification_type_report_post extends phpbb_notification_type_post_in_queue
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'report_post';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_REPORT_POST';
+
+ /**
+ * Permission to check for (in find_users_for_notification)
+ *
+ * @var string Permission name
+ */
+ protected $permission = 'm_report';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id' and 'lang')
+ */
+ public static $notification_option = array(
+ 'id' => 'report',
+ 'lang' => 'NOTIFICATION_TYPE_REPORT',
+ 'group' => 'NOTIFICATION_GROUP_MODERATION',
+ );
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $post Data from the post
+ *
+ * @return array
+ */
+ public function find_users_for_notification($post, $options = array())
+ {
+ $notify_users = parent::find_users_for_notification($post, $options);
+
+ // never notify reporter
+ unset($notify_users[$this->user->data['user_id']]);
+
+ return $notify_users;
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'report_post';
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ $board_url = generate_board_url();
+
+ return array(
+ 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))),
+ 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))),
+
+ 'U_VIEW_REPORT' => "{$board_url}/mcp.{$this->php_ext}?f={$this->get_data('forum_id')}&amp;p={$this->item_id}&amp;i=reports&amp;mode=report_details#reports",
+ 'U_VIEW_POST' => "{$board_url}/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}",
+ 'U_NEWEST_POST' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&view=unread#unread",
+ 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}",
+ 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}",
+ 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}",
+ );
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "f={$this->get_data('forum_id')}&amp;p={$this->item_id}&amp;i=reports&amp;mode=report_details#reports");
+ }
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title()
+ {
+ $this->user->add_lang('mcp');
+
+ $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile');
+
+ if ($this->get_data('report_text'))
+ {
+ return $this->user->lang(
+ $this->language_key,
+ $username,
+ censor_text($this->get_data('post_subject')),
+ $this->get_data('report_text')
+ );
+ }
+
+ if (isset($this->user->lang[$this->get_data('reason_title')]))
+ {
+ return $this->user->lang(
+ $this->language_key,
+ $username,
+ censor_text($this->get_data('post_subject')),
+ $this->user->lang[$this->get_data('reason_title')]
+ );
+ }
+
+ return $this->user->lang(
+ $this->language_key,
+ $username,
+ censor_text($this->get_data('post_subject')),
+ $this->get_data('reason_description')
+ );
+ }
+
+ /**
+ * Get the user's avatar
+ */
+ public function get_avatar()
+ {
+ return $this->user_loader->get_avatar($this->get_data('reporter_id'));
+ }
+
+ /**
+ * Users needed to query before this notification can be displayed
+ *
+ * @return array Array of user_ids
+ */
+ public function users_to_query()
+ {
+ return array($this->get_data('reporter_id'));
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $this->set_data('reporter_id', $this->user->data['user_id']);
+ $this->set_data('reason_title', strtoupper($post['reason_title']));
+ $this->set_data('reason_description', $post['reason_description']);
+ $this->set_data('report_text', $post['report_text']);
+
+ return parent::create_insert_array($post, $pre_create_data);
+ }
+}
diff --git a/phpBB/includes/notification/type/report_post_closed.php b/phpBB/includes/notification/type/report_post_closed.php
new file mode 100644
index 0000000000..3916cd8db7
--- /dev/null
+++ b/phpBB/includes/notification/type/report_post_closed.php
@@ -0,0 +1,155 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Post report closed notifications class
+* This class handles notifications for when reports are closed on posts (for the one who reported the post)
+*
+* @package notifications
+*/
+class phpbb_notification_type_report_post_closed extends phpbb_notification_type_post
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'report_post_closed';
+ }
+
+ /**
+ * Email template to use to send notifications
+ *
+ * @var string
+ */
+ public $email_template = '';
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_REPORT_CLOSED';
+
+ public function is_available()
+ {
+ return false;
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $post Data from
+ *
+ * @return array
+ */
+ public function find_users_for_notification($post, $options = array())
+ {
+ if ($post['reporter'] == $this->user->data['user_id'])
+ {
+ return array();
+ }
+
+ return array($post['reporter'] => array(''));
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return false;
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ return array();
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return '';
+ }
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title()
+ {
+ $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile');
+
+ return $this->user->lang(
+ $this->language_key,
+ $username,
+ censor_text($this->get_data('post_subject'))
+ );
+ }
+
+ /**
+ * Get the user's avatar
+ */
+ public function get_avatar()
+ {
+ return $this->user_loader->get_avatar($this->get_data('closer_id'));
+ }
+
+ /**
+ * Users needed to query before this notification can be displayed
+ *
+ * @return array Array of user_ids
+ */
+ public function users_to_query()
+ {
+ return array($this->get_data('closer_id'));
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $this->set_data('closer_id', $post['closer_id']);
+
+ $data = parent::create_insert_array($post, $pre_create_data);
+
+ $this->notification_time = $data['notification_time'] = time();
+
+ return $data;
+ }
+}
diff --git a/phpBB/includes/notification/type/topic.php b/phpBB/includes/notification/type/topic.php
new file mode 100644
index 0000000000..2549b29409
--- /dev/null
+++ b/phpBB/includes/notification/type/topic.php
@@ -0,0 +1,277 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Topic notifications class
+* This class handles notifications for new topics
+*
+* @package notifications
+*/
+class phpbb_notification_type_topic extends phpbb_notification_type_base
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'topic';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_TOPIC';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'lang' => 'NOTIFICATION_TYPE_TOPIC',
+ 'group' => 'NOTIFICATION_GROUP_POSTING',
+ );
+
+ /**
+ * Is available
+ */
+ public function is_available()
+ {
+ return $this->config['allow_forum_notify'];
+ }
+
+ /**
+ * Get the id of the item
+ *
+ * @param array $post The data from the post
+ */
+ public static function get_item_id($post)
+ {
+ return (int) $post['topic_id'];
+ }
+
+ /**
+ * Get the id of the parent
+ *
+ * @param array $post The data from the post
+ */
+ public static function get_item_parent_id($post)
+ {
+ return (int) $post['forum_id'];
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $topic Data from the topic
+ *
+ * @return array
+ */
+ public function find_users_for_notification($topic, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ $users = array();
+
+ $sql = 'SELECT user_id
+ FROM ' . FORUMS_WATCH_TABLE . '
+ WHERE forum_id = ' . (int) $topic['forum_id'] . '
+ AND notify_status = ' . NOTIFY_YES . '
+ AND user_id <> ' . (int) $topic['poster_id'];
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $users[] = $row['user_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (empty($users))
+ {
+ return array();
+ }
+
+ $auth_read = $this->auth->acl_get_list($users, 'f_read', $topic['forum_id']);
+
+ if (empty($auth_read))
+ {
+ return array();
+ }
+
+ return $this->check_user_notification_options($auth_read[$topic['forum_id']]['f_read'], $options);
+ }
+
+ /**
+ * Get the user's avatar
+ */
+ public function get_avatar()
+ {
+ return $this->user_loader->get_avatar($this->get_data('poster_id'));
+ }
+
+ /**
+ * Get the HTML formatted title of this notification
+ *
+ * @return string
+ */
+ public function get_title()
+ {
+ if ($this->get_data('post_username'))
+ {
+ $username = $this->get_data('post_username');
+ }
+ else
+ {
+ $username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile');
+ }
+
+ return $this->user->lang(
+ $this->language_key,
+ $username,
+ censor_text($this->get_data('topic_title')),
+ $this->get_data('forum_name')
+ );
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'newtopic_notify';
+ }
+
+ /**
+ * Get email template variables
+ *
+ * @return array
+ */
+ public function get_email_template_variables()
+ {
+ $board_url = generate_board_url();
+
+ if ($this->get_data('post_username'))
+ {
+ $username = $this->get_data('post_username');
+ }
+ else
+ {
+ $username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile');
+ }
+
+ return array(
+ 'AUTHOR_NAME' => htmlspecialchars_decode($username),
+ 'FORUM_NAME' => htmlspecialchars_decode($this->get_data('forum_name')),
+ 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))),
+
+ 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}",
+ 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}",
+ 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->item_parent_id}",
+ 'U_STOP_WATCHING_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?uid={$this->user_id}&f={$this->item_parent_id}&unwatch=forum",
+ );
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "f={$this->item_parent_id}&amp;t={$this->item_id}");
+ }
+
+ /**
+ * Users needed to query before this notification can be displayed
+ *
+ * @return array Array of user_ids
+ */
+ public function users_to_query()
+ {
+ return array($this->get_data('poster_id'));
+ }
+
+ /**
+ * Pre create insert array function
+ * This allows you to perform certain actions, like run a query
+ * and load data, before create_insert_array() is run. The data
+ * returned from this function will be sent to create_insert_array().
+ *
+ * @param array $post Post data from submit_post
+ * @param array $notify_users Notify users list
+ * Formated from find_users_for_notification()
+ * @return array Whatever you want to send to create_insert_array().
+ */
+ public function pre_create_insert_array($post, $notify_users)
+ {
+ if (!sizeof($notify_users))
+ {
+ return array();
+ }
+
+ $tracking_data = array();
+ $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . '
+ WHERE topic_id = ' . (int) $post['topic_id'] . '
+ AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users));
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $tracking_data[$row['user_id']] = $row['mark_time'];
+ }
+
+ return $tracking_data;
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $post Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($post, $pre_create_data = array())
+ {
+ $this->set_data('poster_id', $post['poster_id']);
+
+ $this->set_data('topic_title', $post['topic_title']);
+
+ $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''));
+
+ $this->set_data('forum_name', $post['forum_name']);
+
+ $this->notification_time = $post['post_time'];
+
+ // Topics can be "read" before they are public (while awaiting approval).
+ // Make sure that if the user has read the topic, it's marked as read in the notification
+ if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time)
+ {
+ $this->notification_read = true;
+ }
+
+ return parent::create_insert_array($post, $pre_create_data);
+ }
+}
diff --git a/phpBB/includes/notification/type/topic_in_queue.php b/phpBB/includes/notification/type/topic_in_queue.php
new file mode 100644
index 0000000000..dc0b9f9869
--- /dev/null
+++ b/phpBB/includes/notification/type/topic_in_queue.php
@@ -0,0 +1,130 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Topic in queue notifications class
+* This class handles notifications for topics when they are put in the moderation queue (for moderators)
+*
+* @package notifications
+*/
+class phpbb_notification_type_topic_in_queue extends phpbb_notification_type_topic
+{
+ /**
+ * Get notification type name
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'topic_in_queue';
+ }
+
+ /**
+ * Language key used to output the text
+ *
+ * @var string
+ */
+ protected $language_key = 'NOTIFICATION_TOPIC_IN_QUEUE';
+
+ /**
+ * Notification option data (for outputting to the user)
+ *
+ * @var bool|array False if the service should use it's default data
+ * Array of data (including keys 'id', 'lang', and 'group')
+ */
+ public static $notification_option = array(
+ 'id' => 'needs_approval',
+ 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE',
+ 'group' => 'NOTIFICATION_GROUP_MODERATION',
+ );
+
+ /**
+ * Is available
+ */
+ public function is_available()
+ {
+ $m_approve = $this->auth->acl_getf('m_approve', true);
+
+ return (!empty($m_approve));
+ }
+
+ /**
+ * Find the users who want to receive notifications
+ *
+ * @param array $topic Data from the topic
+ *
+ * @return array
+ */
+ public function find_users_for_notification($topic, $options = array())
+ {
+ $options = array_merge(array(
+ 'ignore_users' => array(),
+ ), $options);
+
+ // 0 is for global
+ $auth_approve = $this->auth->acl_get_list(false, 'm_approve', array($topic['forum_id'], 0));
+
+ if (empty($auth_approve))
+ {
+ return array();
+ }
+
+ $auth_approve[$topic['forum_id']] = array_unique(array_merge($auth_approve[$topic['forum_id']], $auth_approve[0]));
+
+ return $this->check_user_notification_options($auth_approve[$topic['forum_id']]['m_approve'], array_merge($options, array(
+ 'item_type' => self::$notification_option['id'],
+ )));
+ }
+
+ /**
+ * Get the url to this item
+ *
+ * @return string URL
+ */
+ public function get_url()
+ {
+ return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&amp;mode=approve_details&amp;f={$this->item_parent_id}&amp;t={$this->item_id}");
+ }
+
+ /**
+ * Function for preparing the data for insertion in an SQL query
+ * (The service handles insertion)
+ *
+ * @param array $topic Data from submit_post
+ * @param array $pre_create_data Data from pre_create_insert_array()
+ *
+ * @return array Array of data ready to be inserted into the database
+ */
+ public function create_insert_array($topic, $pre_create_data = array())
+ {
+ $data = parent::create_insert_array($topic, $pre_create_data);
+
+ $this->notification_time = $data['notification_time'] = time();
+
+ return $data;
+ }
+
+ /**
+ * Get email template
+ *
+ * @return string|bool
+ */
+ public function get_email_template()
+ {
+ return 'topic_in_queue';
+ }
+}
diff --git a/phpBB/includes/search/base.php b/phpBB/includes/search/base.php
index b364dead9a..914cef9167 100644
--- a/phpBB/includes/search/base.php
+++ b/phpBB/includes/search/base.php
@@ -94,7 +94,7 @@ class phpbb_search_base
*
* @return int SEARCH_RESULT_NOT_IN_CACHE or SEARCH_RESULT_IN_CACHE or SEARCH_RESULT_INCOMPLETE
*/
- function obtain_ids($search_key, &$result_count, &$id_ary, $start, $per_page, $sort_dir)
+ function obtain_ids($search_key, &$result_count, &$id_ary, &$start, $per_page, $sort_dir)
{
global $cache;
@@ -109,6 +109,19 @@ class phpbb_search_base
$reverse_ids = ($stored_ids[-2] != $sort_dir) ? true : false;
$complete = true;
+ // Change start parameter in case out of bounds
+ if ($result_count)
+ {
+ if ($start < 0)
+ {
+ $start = 0;
+ }
+ else if ($start >= $result_count)
+ {
+ $start = floor(($result_count - 1) / $per_page) * $per_page;
+ }
+ }
+
// change the start to the actual end of the current request if the sort direction differs
// from the dirction in the cache and reverse the ids later
if ($reverse_ids)
diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php
index 913334bd08..e3ec56423e 100644
--- a/phpBB/includes/search/fulltext_mysql.php
+++ b/phpBB/includes/search/fulltext_mysql.php
@@ -353,7 +353,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base
* @param int $per_page number of ids each page is supposed to contain
* @return boolean|int total number of results
*/
- public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page)
+ public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page)
{
// No keywords? No posts
if (!$this->search_query)
@@ -375,6 +375,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base
implode(',', $author_ary)
)));
+ if ($start < 0)
+ {
+ $start = 0;
+ }
+
// try reading the results from cache
$result_count = 0;
if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE)
@@ -475,16 +480,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base
$id_ary = array_unique($id_ary);
- if (!sizeof($id_ary))
- {
- return false;
- }
-
// if the total result count is not cached yet, retrieve it from the db
if (!$result_count)
{
- $sql = 'SELECT FOUND_ROWS() as result_count';
- $result = $this->db->sql_query($sql);
+ $sql_found_rows = 'SELECT FOUND_ROWS() as result_count';
+ $result = $this->db->sql_query($sql_found_rows);
$result_count = (int) $this->db->sql_fetchfield('result_count');
$this->db->sql_freeresult($result);
@@ -494,6 +494,21 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base
}
}
+ if ($start >= $result_count)
+ {
+ $start = floor(($result_count - 1) / $per_page) * $per_page;
+
+ $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $id_ary[] = (int) $row[$field];
+ }
+ $this->db->sql_freeresult($result);
+
+ $id_ary = array_unique($id_ary);
+ }
+
// store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page
$this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir);
$id_ary = array_slice($id_ary, 0, (int) $per_page);
@@ -520,7 +535,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base
* @param int $per_page number of ids each page is supposed to contain
* @return boolean|int total number of results
*/
- 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)
+ 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)
{
// No author? No posts
if (!sizeof($author_ary))
@@ -544,6 +559,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base
$author_name,
)));
+ if ($start < 0)
+ {
+ $start = 0;
+ }
+
// try reading the results from cache
$result_count = 0;
if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE)
@@ -638,8 +658,8 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base
// retrieve the total result count if needed
if (!$result_count)
{
- $sql = 'SELECT FOUND_ROWS() as result_count';
- $result = $this->db->sql_query($sql);
+ $sql_found_rows = 'SELECT FOUND_ROWS() as result_count';
+ $result = $this->db->sql_query($sql_found_rows);
$result_count = (int) $this->db->sql_fetchfield('result_count');
$this->db->sql_freeresult($result);
@@ -649,6 +669,20 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base
}
}
+ if ($start >= $result_count)
+ {
+ $start = floor(($result_count - 1) / $per_page) * $per_page;
+
+ $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $id_ary[] = (int) $row[$field];
+ }
+ $this->db->sql_freeresult($result);
+
+ $id_ary = array_unique($id_ary);
+ }
+
if (sizeof($id_ary))
{
$this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir);
diff --git a/phpBB/includes/search/fulltext_native.php b/phpBB/includes/search/fulltext_native.php
index 3dc44be682..90ea553ddc 100644
--- a/phpBB/includes/search/fulltext_native.php
+++ b/phpBB/includes/search/fulltext_native.php
@@ -516,7 +516,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base
* @param int $per_page number of ids each page is supposed to contain
* @return boolean|int total number of results
*/
- public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page)
+ public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page)
{
// No keywords? No posts.
if (empty($this->search_query))
@@ -848,10 +848,6 @@ class phpbb_search_fulltext_native extends phpbb_search_base
}
$this->db->sql_freeresult($result);
- if (!sizeof($id_ary))
- {
- return false;
- }
// if we use mysql and the total result count is not cached yet, retrieve it from the db
if (!$total_results && $is_mysql)
@@ -860,14 +856,14 @@ class phpbb_search_fulltext_native extends phpbb_search_base
$sql_array_copy = $sql_array;
$sql_array_copy['SELECT'] = 'SQL_CALC_FOUND_ROWS p.post_id ';
- $sql = $this->db->sql_build_query('SELECT', $sql_array_copy);
+ $sql_calc = $this->db->sql_build_query('SELECT', $sql_array_copy);
unset($sql_array_copy);
- $this->db->sql_query($sql);
+ $this->db->sql_query($sql_calc);
$this->db->sql_freeresult($result);
- $sql = 'SELECT FOUND_ROWS() as total_results';
- $result = $this->db->sql_query($sql);
+ $sql_count = 'SELECT FOUND_ROWS() as total_results';
+ $result = $this->db->sql_query($sql_count);
$total_results = (int) $this->db->sql_fetchfield('total_results');
$this->db->sql_freeresult($result);
@@ -877,6 +873,20 @@ class phpbb_search_fulltext_native extends phpbb_search_base
}
}
+ if ($start >= $total_results)
+ {
+ $start = floor(($total_results - 1) / $per_page) * $per_page;
+
+ $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')];
+ }
+ $this->db->sql_freeresult($result);
+
+ }
+
// store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page
$this->save_ids($search_key, $this->search_query, $author_ary, $total_results, $id_ary, $start, $sort_dir);
$id_ary = array_slice($id_ary, 0, (int) $per_page);
@@ -903,7 +913,11 @@ class phpbb_search_fulltext_native extends phpbb_search_base
* @param int $per_page number of ids each page is supposed to contain
* @return boolean|int total number of results
*/
+<<<<<<< HEAD
public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page)
+=======
+ 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)
+>>>>>>> bee4f8d8185d4ff5278be758db4ea4a814f09b4f
{
// No author? No posts
if (!sizeof($author_ary))
@@ -1077,13 +1091,13 @@ class phpbb_search_fulltext_native extends phpbb_search_base
if (!$total_results && $is_mysql)
{
// Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it.
- $sql = str_replace('SELECT ' . $select, 'SELECT DISTINCT SQL_CALC_FOUND_ROWS p.post_id', $sql);
+ $sql_calc = str_replace('SELECT ' . $select, 'SELECT DISTINCT SQL_CALC_FOUND_ROWS p.post_id', $sql);
- $this->db->sql_query($sql);
+ $this->db->sql_query($sql_calc);
$this->db->sql_freeresult($result);
- $sql = 'SELECT FOUND_ROWS() as total_results';
- $result = $this->db->sql_query($sql);
+ $sql_count = 'SELECT FOUND_ROWS() as total_results';
+ $result = $this->db->sql_query($sql_count);
$total_results = (int) $this->db->sql_fetchfield('total_results');
$this->db->sql_freeresult($result);
@@ -1093,6 +1107,19 @@ class phpbb_search_fulltext_native extends phpbb_search_base
}
}
+ if ($start >= $total_results)
+ {
+ $start = floor(($total_results - 1) / $per_page) * $per_page;
+
+ $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $id_ary[] = (int) $row[$field];
+ }
+ $this->db->sql_freeresult($result);
+ }
+
if (sizeof($id_ary))
{
$this->save_ids($search_key, '', $author_ary, $total_results, $id_ary, $start, $sort_dir);
diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php
index 71ef347a1b..d5deb47222 100644
--- a/phpBB/includes/search/fulltext_postgres.php
+++ b/phpBB/includes/search/fulltext_postgres.php
@@ -343,7 +343,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
* @param int $per_page number of ids each page is supposed to contain
* @return boolean|int total number of results
*/
- public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page)
+ public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page)
{
// No keywords? No posts
if (!$this->search_query)
@@ -371,6 +371,11 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
implode(',', $author_ary)
)));
+ if ($start < 0)
+ {
+ $start = 0;
+ }
+
// try reading the results from cache
$result_count = 0;
if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE)
@@ -463,10 +468,14 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
$tmp_sql_match[] = "to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', '" . $this->db->sql_escape($this->tsearch_query) . "')";
}
+ $this->db->sql_transaction('begin');
+
+ $sql_from = "FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p";
+ $sql_where = "WHERE (" . implode(' OR ', $tmp_sql_match) . ")
+ $sql_where_options";
$sql = "SELECT $sql_select
- FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p
- WHERE (" . implode(' OR ', $tmp_sql_match) . ")
- $sql_where_options
+ $sql_from
+ $sql_where
ORDER BY $sql_sort";
$result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start);
@@ -478,15 +487,15 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
$id_ary = array_unique($id_ary);
- if (!sizeof($id_ary))
- {
- return false;
- }
-
// if the total result count is not cached yet, retrieve it from the db
if (!$result_count)
{
- $result_count = sizeof ($id_ary);
+ $sql_count = "SELECT COUNT(*) as result_count
+ $sql_from
+ $sql_where";
+ $result = $this->db->sql_query($sql_count);
+ $result_count = (int) $this->db->sql_fetchfield('result_count');
+ $this->db->sql_freeresult($result);
if (!$result_count)
{
@@ -494,6 +503,23 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
}
}
+ $this->db->sql_transaction('commit');
+
+ if ($start >= $result_count)
+ {
+ $start = floor(($result_count - 1) / $per_page) * $per_page;
+
+ $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $id_ary[] = $row[$field];
+ }
+ $this->db->sql_freeresult($result);
+
+ $id_ary = array_unique($id_ary);
+ }
+
// store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page
$this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir);
$id_ary = array_slice($id_ary, 0, (int) $per_page);
@@ -520,7 +546,11 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
* @param int $per_page number of ids each page is supposed to contain
* @return boolean|int total number of results
*/
+<<<<<<< HEAD
public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page)
+=======
+ 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)
+>>>>>>> bee4f8d8185d4ff5278be758db4ea4a814f09b4f
{
// No author? No posts
if (!sizeof($author_ary))
@@ -544,6 +574,11 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
$author_name,
)));
+ if ($start < 0)
+ {
+ $start = 0;
+ }
+
// try reading the results from cache
$result_count = 0;
if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE)
@@ -623,6 +658,8 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
$field = 'topic_id';
}
+ $this->db->sql_transaction('begin');
+
// Only read one block of posts from the db and then cache it
$result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start);
@@ -635,7 +672,35 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
// retrieve the total result count if needed
if (!$result_count)
{
- $result_count = sizeof ($id_ary);
+ if ($type == 'posts')
+ {
+ $sql_count = "SELECT COUNT(*) as result_count
+ FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . "
+ WHERE $sql_author
+ $sql_topic_id
+ $sql_firstpost
+ $m_approve_fid_sql
+ $sql_fora
+ $sql_sort_join
+ $sql_time";
+ }
+ else
+ {
+ $sql_count = "SELECT COUNT(*) as result_count
+ FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
+ WHERE $sql_author
+ $sql_topic_id
+ $sql_firstpost
+ $m_approve_fid_sql
+ $sql_fora
+ AND t.topic_id = p.topic_id
+ $sql_sort_join
+ $sql_time
+ GROUP BY t.topic_id, $sort_by_sql[$sort_key]";
+ }
+
+ $result = $this->db->sql_query($sql_count);
+ $result_count = (int) $this->db->sql_fetchfield('result_count');
if (!$result_count)
{
@@ -643,6 +708,22 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
}
}
+ $this->db->sql_transaction('commit');
+
+ if ($start >= $result_count)
+ {
+ $start = floor(($result_count - 1) / $per_page) * $per_page;
+
+ $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $id_ary[] = (int) $row[$field];
+ }
+ $this->db->sql_freeresult($result);
+
+ $id_ary = array_unique($id_ary);
+ }
+
if (sizeof($id_ary))
{
$this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir);
diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php
index 7c0be91083..441fb2583b 100644
--- a/phpBB/includes/search/fulltext_sphinx.php
+++ b/phpBB/includes/search/fulltext_sphinx.php
@@ -454,7 +454,7 @@ class phpbb_search_fulltext_sphinx
* @param int $per_page number of ids each page is supposed to contain
* @return boolean|int total number of results
*/
- public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page)
+ public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page)
{
// No keywords? No posts.
if (!strlen($this->search_query) && !sizeof($author_ary))
@@ -611,6 +611,25 @@ class phpbb_search_fulltext_sphinx
}
}
+ $result_count = $result['total_found'];
+
+ if ($start >= $result_count)
+ {
+ $start = floor(($result_count - 1) / $per_page) * $per_page;
+
+ $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES);
+ $result = $this->sphinx->Query($search_query_prefix . str_replace('&quot;', '"', $this->search_query), $this->indexes);
+
+ // Could be connection to localhost:9312 failed (errno=111,
+ // msg=Connection refused) during rotate, retry if so
+ $retries = SPHINX_CONNECT_RETRIES;
+ while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--)
+ {
+ usleep(SPHINX_CONNECT_WAIT_TIME);
+ $result = $this->sphinx->Query($search_query_prefix . str_replace('&quot;', '"', $this->search_query), $this->indexes);
+ }
+ }
+
$id_ary = array();
if (isset($result['matches']))
{
@@ -631,8 +650,6 @@ class phpbb_search_fulltext_sphinx
return false;
}
- $result_count = $result['total_found'];
-
$id_ary = array_slice($id_ary, 0, (int) $per_page);
return $result_count;
@@ -880,8 +897,8 @@ class phpbb_search_fulltext_sphinx
<dd><input id="fulltext_sphinx_indexer_mem_limit" type="text" size="4" maxlength="10" name="config[fulltext_sphinx_indexer_mem_limit]" value="' . $this->config['fulltext_sphinx_indexer_mem_limit'] . '" /> ' . $this->user->lang['MIB'] . '</dd>
</dl>
<dl>
- <dt><label for="fulltext_sphinx_config_file">' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '</dt>
- <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6">' . $this->config_file_data . '</textarea>' : $this->config_file_data) . '</dd>
+ <dt><label for="fulltext_sphinx_config_file">' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '</span></dt>
+ <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6" id="sphinx_config_data">' . htmlspecialchars($this->config_file_data) . '</textarea>' : $this->config_file_data) . '</dd>
<dl>
';
diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php
index ee8a4094c7..6bc71da0c1 100644
--- a/phpBB/includes/session.php
+++ b/phpBB/includes/session.php
@@ -346,7 +346,7 @@ class phpbb_session
$session_id = $request->variable('sid', '');
if (defined('NEED_SID') && (empty($session_id) || $this->session_id !== $session_id))
{
- send_status_line(401, 'Not authorized');
+ send_status_line(401, 'Unauthorized');
redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
}
diff --git a/phpBB/includes/sphinxapi.php b/phpBB/includes/sphinxapi.php
index bd83b1d2e0..6c3b66710c 100644
--- a/phpBB/includes/sphinxapi.php
+++ b/phpBB/includes/sphinxapi.php
@@ -1,1712 +1,1712 @@
-<?php
-
-//
-// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
-//
-
-//
-// Copyright (c) 2001-2012, Andrew Aksyonoff
-// Copyright (c) 2008-2012, Sphinx Technologies Inc
-// All rights reserved
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License. You should have
-// received a copy of the GPL license along with this program; if you
-// did not, you can find it at http://www.gnu.org/
-//
-
-/////////////////////////////////////////////////////////////////////////////
-// PHP version of Sphinx searchd client (PHP API)
-/////////////////////////////////////////////////////////////////////////////
-
-/// known searchd commands
-define ( "SEARCHD_COMMAND_SEARCH", 0 );
-define ( "SEARCHD_COMMAND_EXCERPT", 1 );
-define ( "SEARCHD_COMMAND_UPDATE", 2 );
-define ( "SEARCHD_COMMAND_KEYWORDS", 3 );
-define ( "SEARCHD_COMMAND_PERSIST", 4 );
-define ( "SEARCHD_COMMAND_STATUS", 5 );
-define ( "SEARCHD_COMMAND_FLUSHATTRS", 7 );
-
-/// current client-side command implementation versions
-define ( "VER_COMMAND_SEARCH", 0x119 );
-define ( "VER_COMMAND_EXCERPT", 0x104 );
-define ( "VER_COMMAND_UPDATE", 0x102 );
-define ( "VER_COMMAND_KEYWORDS", 0x100 );
-define ( "VER_COMMAND_STATUS", 0x100 );
-define ( "VER_COMMAND_QUERY", 0x100 );
-define ( "VER_COMMAND_FLUSHATTRS", 0x100 );
-
-/// known searchd status codes
-define ( "SEARCHD_OK", 0 );
-define ( "SEARCHD_ERROR", 1 );
-define ( "SEARCHD_RETRY", 2 );
-define ( "SEARCHD_WARNING", 3 );
-
-/// known match modes
-define ( "SPH_MATCH_ALL", 0 );
-define ( "SPH_MATCH_ANY", 1 );
-define ( "SPH_MATCH_PHRASE", 2 );
-define ( "SPH_MATCH_BOOLEAN", 3 );
-define ( "SPH_MATCH_EXTENDED", 4 );
-define ( "SPH_MATCH_FULLSCAN", 5 );
-define ( "SPH_MATCH_EXTENDED2", 6 ); // extended engine V2 (TEMPORARY, WILL BE REMOVED)
-
-/// known ranking modes (ext2 only)
-define ( "SPH_RANK_PROXIMITY_BM25", 0 ); ///< default mode, phrase proximity major factor and BM25 minor one
-define ( "SPH_RANK_BM25", 1 ); ///< statistical mode, BM25 ranking only (faster but worse quality)
-define ( "SPH_RANK_NONE", 2 ); ///< no ranking, all matches get a weight of 1
-define ( "SPH_RANK_WORDCOUNT", 3 ); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
-define ( "SPH_RANK_PROXIMITY", 4 );
-define ( "SPH_RANK_MATCHANY", 5 );
-define ( "SPH_RANK_FIELDMASK", 6 );
-define ( "SPH_RANK_SPH04", 7 );
-define ( "SPH_RANK_EXPR", 8 );
-define ( "SPH_RANK_TOTAL", 9 );
-
-/// known sort modes
-define ( "SPH_SORT_RELEVANCE", 0 );
-define ( "SPH_SORT_ATTR_DESC", 1 );
-define ( "SPH_SORT_ATTR_ASC", 2 );
-define ( "SPH_SORT_TIME_SEGMENTS", 3 );
-define ( "SPH_SORT_EXTENDED", 4 );
-define ( "SPH_SORT_EXPR", 5 );
-
-/// known filter types
-define ( "SPH_FILTER_VALUES", 0 );
-define ( "SPH_FILTER_RANGE", 1 );
-define ( "SPH_FILTER_FLOATRANGE", 2 );
-
-/// known attribute types
-define ( "SPH_ATTR_INTEGER", 1 );
-define ( "SPH_ATTR_TIMESTAMP", 2 );
-define ( "SPH_ATTR_ORDINAL", 3 );
-define ( "SPH_ATTR_BOOL", 4 );
-define ( "SPH_ATTR_FLOAT", 5 );
-define ( "SPH_ATTR_BIGINT", 6 );
-define ( "SPH_ATTR_STRING", 7 );
-define ( "SPH_ATTR_MULTI", 0x40000001 );
-define ( "SPH_ATTR_MULTI64", 0x40000002 );
-
-/// known grouping functions
-define ( "SPH_GROUPBY_DAY", 0 );
-define ( "SPH_GROUPBY_WEEK", 1 );
-define ( "SPH_GROUPBY_MONTH", 2 );
-define ( "SPH_GROUPBY_YEAR", 3 );
-define ( "SPH_GROUPBY_ATTR", 4 );
-define ( "SPH_GROUPBY_ATTRPAIR", 5 );
-
-// important properties of PHP's integers:
-// - always signed (one bit short of PHP_INT_SIZE)
-// - conversion from string to int is saturated
-// - float is double
-// - div converts arguments to floats
-// - mod converts arguments to ints
-
-// the packing code below works as follows:
-// - when we got an int, just pack it
-// if performance is a problem, this is the branch users should aim for
-//
-// - otherwise, we got a number in string form
-// this might be due to different reasons, but we assume that this is
-// because it didn't fit into PHP int
-//
-// - factor the string into high and low ints for packing
-// - if we have bcmath, then it is used
-// - if we don't, we have to do it manually (this is the fun part)
-//
-// - x64 branch does factoring using ints
-// - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
-//
-// unpacking routines are pretty much the same.
-// - return ints if we can
-// - otherwise format number into a string
-
-/// pack 64-bit signed
-function sphPackI64 ( $v )
-{
- assert ( is_numeric($v) );
-
- // x64
- if ( PHP_INT_SIZE>=8 )
- {
- $v = (int)$v;
- return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
- }
-
- // x32, int
- if ( is_int($v) )
- return pack ( "NN", $v < 0 ? -1 : 0, $v );
-
- // x32, bcmath
- if ( function_exists("bcmul") )
- {
- if ( bccomp ( $v, 0 ) == -1 )
- $v = bcadd ( "18446744073709551616", $v );
- $h = bcdiv ( $v, "4294967296", 0 );
- $l = bcmod ( $v, "4294967296" );
- return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
- }
-
- // x32, no-bcmath
- $p = max(0, strlen($v) - 13);
- $lo = abs((float)substr($v, $p));
- $hi = abs((float)substr($v, 0, $p));
-
- $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
- $q = floor($m/4294967296.0);
- $l = $m - ($q*4294967296.0);
- $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
-
- if ( $v<0 )
- {
- if ( $l==0 )
- $h = 4294967296.0 - $h;
- else
- {
- $h = 4294967295.0 - $h;
- $l = 4294967296.0 - $l;
- }
- }
- return pack ( "NN", $h, $l );
-}
-
-/// pack 64-bit unsigned
-function sphPackU64 ( $v )
-{
- assert ( is_numeric($v) );
-
- // x64
- if ( PHP_INT_SIZE>=8 )
- {
- assert ( $v>=0 );
-
- // x64, int
- if ( is_int($v) )
- return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
-
- // x64, bcmath
- if ( function_exists("bcmul") )
- {
- $h = bcdiv ( $v, 4294967296, 0 );
- $l = bcmod ( $v, 4294967296 );
- return pack ( "NN", $h, $l );
- }
-
- // x64, no-bcmath
- $p = max ( 0, strlen($v) - 13 );
- $lo = (int)substr ( $v, $p );
- $hi = (int)substr ( $v, 0, $p );
-
- $m = $lo + $hi*1316134912;
- $l = $m % 4294967296;
- $h = $hi*2328 + (int)($m/4294967296);
-
- return pack ( "NN", $h, $l );
- }
-
- // x32, int
- if ( is_int($v) )
- return pack ( "NN", 0, $v );
-
- // x32, bcmath
- if ( function_exists("bcmul") )
- {
- $h = bcdiv ( $v, "4294967296", 0 );
- $l = bcmod ( $v, "4294967296" );
- return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
- }
-
- // x32, no-bcmath
- $p = max(0, strlen($v) - 13);
- $lo = (float)substr($v, $p);
- $hi = (float)substr($v, 0, $p);
-
- $m = $lo + $hi*1316134912.0;
- $q = floor($m / 4294967296.0);
- $l = $m - ($q * 4294967296.0);
- $h = $hi*2328.0 + $q;
-
- return pack ( "NN", $h, $l );
-}
-
-// unpack 64-bit unsigned
-function sphUnpackU64 ( $v )
-{
- list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
-
- if ( PHP_INT_SIZE>=8 )
- {
- if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
- if ( $lo<0 ) $lo += (1<<32);
-
- // x64, int
- if ( $hi<=2147483647 )
- return ($hi<<32) + $lo;
-
- // x64, bcmath
- if ( function_exists("bcmul") )
- return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
-
- // x64, no-bcmath
- $C = 100000;
- $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
- $l = (($hi % $C) << 32) + ($lo % $C);
- if ( $l>$C )
- {
- $h += (int)($l / $C);
- $l = $l % $C;
- }
-
- if ( $h==0 )
- return $l;
- return sprintf ( "%d%05d", $h, $l );
- }
-
- // x32, int
- if ( $hi==0 )
- {
- if ( $lo>0 )
- return $lo;
- return sprintf ( "%u", $lo );
- }
-
- $hi = sprintf ( "%u", $hi );
- $lo = sprintf ( "%u", $lo );
-
- // x32, bcmath
- if ( function_exists("bcmul") )
- return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
-
- // x32, no-bcmath
- $hi = (float)$hi;
- $lo = (float)$lo;
-
- $q = floor($hi/10000000.0);
- $r = $hi - $q*10000000.0;
- $m = $lo + $r*4967296.0;
- $mq = floor($m/10000000.0);
- $l = $m - $mq*10000000.0;
- $h = $q*4294967296.0 + $r*429.0 + $mq;
-
- $h = sprintf ( "%.0f", $h );
- $l = sprintf ( "%07.0f", $l );
- if ( $h=="0" )
- return sprintf( "%.0f", (float)$l );
- return $h . $l;
-}
-
-// unpack 64-bit signed
-function sphUnpackI64 ( $v )
-{
- list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
-
- // x64
- if ( PHP_INT_SIZE>=8 )
- {
- if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
- if ( $lo<0 ) $lo += (1<<32);
-
- return ($hi<<32) + $lo;
- }
-
- // x32, int
- if ( $hi==0 )
- {
- if ( $lo>0 )
- return $lo;
- return sprintf ( "%u", $lo );
- }
- // x32, int
- elseif ( $hi==-1 )
- {
- if ( $lo<0 )
- return $lo;
- return sprintf ( "%.0f", $lo - 4294967296.0 );
- }
-
- $neg = "";
- $c = 0;
- if ( $hi<0 )
- {
- $hi = ~$hi;
- $lo = ~$lo;
- $c = 1;
- $neg = "-";
- }
-
- $hi = sprintf ( "%u", $hi );
- $lo = sprintf ( "%u", $lo );
-
- // x32, bcmath
- if ( function_exists("bcmul") )
- return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
-
- // x32, no-bcmath
- $hi = (float)$hi;
- $lo = (float)$lo;
-
- $q = floor($hi/10000000.0);
- $r = $hi - $q*10000000.0;
- $m = $lo + $r*4967296.0;
- $mq = floor($m/10000000.0);
- $l = $m - $mq*10000000.0 + $c;
- $h = $q*4294967296.0 + $r*429.0 + $mq;
- if ( $l==10000000 )
- {
- $l = 0;
- $h += 1;
- }
-
- $h = sprintf ( "%.0f", $h );
- $l = sprintf ( "%07.0f", $l );
- if ( $h=="0" )
- return $neg . sprintf( "%.0f", (float)$l );
- return $neg . $h . $l;
-}
-
-
-function sphFixUint ( $value )
-{
- if ( PHP_INT_SIZE>=8 )
- {
- // x64 route, workaround broken unpack() in 5.2.2+
- if ( $value<0 ) $value += (1<<32);
- return $value;
- }
- else
- {
- // x32 route, workaround php signed/unsigned braindamage
- return sprintf ( "%u", $value );
- }
-}
-
-
-/// sphinx searchd client class
-class SphinxClient
-{
- var $_host; ///< searchd host (default is "localhost")
- var $_port; ///< searchd port (default is 9312)
- 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 $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR)
- var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
- var $_fieldweights; ///< per-field-name weights
- var $_overrides; ///< per-query attribute values overrides
- var $_select; ///< select-list (attributes or expressions, with optional aliases)
-
- var $_error; ///< last error message
- var $_warning; ///< last warning message
- var $_connerror; ///< connection error vs remote error flag
-
- 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 = 9312;
- $this->_path = false;
- $this->_socket = false;
-
- // 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->_rankexpr = "";
- $this->_maxquerytime= 0;
- $this->_fieldweights= array();
- $this->_overrides = array();
- $this->_select = "*";
-
- $this->_error = ""; // per-reply fields (for single-query case)
- $this->_warning = "";
- $this->_connerror = false;
-
- $this->_reqs = array (); // requests storage (for multi-query case)
- $this->_mbenc = "";
- $this->_arrayresult = false;
- $this->_timeout = 0;
- }
-
- function __destruct()
- {
- if ( $this->_socket !== false )
- fclose ( $this->_socket );
- }
-
- /// get last error message (string)
- function GetLastError ()
- {
- return $this->_error;
- }
-
- /// get last warning message (string)
- function GetLastWarning ()
- {
- return $this->_warning;
- }
-
- /// get last error flag (to tell network connection errors from searchd errors or broken responses)
- function IsConnectError()
- {
- return $this->_connerror;
- }
-
- /// set searchd host name (string) and port (integer)
- function SetServer ( $host, $port = 0 )
- {
- assert ( is_string($host) );
- if ( $host[0] == '/')
- {
- $this->_path = 'unix://' . $host;
- return;
- }
- if ( substr ( $host, 0, 7 )=="unix://" )
- {
- $this->_path = $host;
- return;
- }
-
- assert ( is_int($port) );
- $this->_host = $host;
- $this->_port = $port;
- $this->_path = '';
-
- }
-
- /// set server connection timeout (0 to remove)
- function SetConnectTimeout ( $timeout )
- {
- assert ( is_numeric($timeout) );
- $this->_timeout = $timeout;
- }
-
-
- function _Send ( $handle, $data, $length )
- {
- if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
- {
- $this->_error = 'connection unexpectedly closed (timed out?)';
- $this->_connerror = true;
- return false;
- }
- return true;
- }
-
- /////////////////////////////////////////////////////////////////////////////
-
- /// 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 ()
- {
- if ( $this->_socket!==false )
- {
- // we are in persistent connection mode, so we have a socket
- // however, need to check whether it's still alive
- if ( !@feof ( $this->_socket ) )
- return $this->_socket;
-
- // force reopen
- $this->_socket = false;
- }
-
- $errno = 0;
- $errstr = "";
- $this->_connerror = false;
-
- if ( $this->_path )
- {
- $host = $this->_path;
- $port = 0;
- }
- else
- {
- $host = $this->_host;
- $port = $this->_port;
- }
-
- if ( $this->_timeout<=0 )
- $fp = @fsockopen ( $host, $port, $errno, $errstr );
- else
- $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
-
- if ( !$fp )
- {
- if ( $this->_path )
- $location = $this->_path;
- else
- $location = "{$this->_host}:{$this->_port}";
-
- $errstr = trim ( $errstr );
- $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
- $this->_connerror = true;
- return false;
- }
-
- // send my version
- // this is a subtle part. we must do it before (!) reading back from searchd.
- // because otherwise under some conditions (reported on FreeBSD for instance)
- // TCP stack could throttle write-write-read pattern because of Nagle.
- if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
- {
- fclose ( $fp );
- $this->_error = "failed to send client protocol version";
- return false;
- }
-
- // check version
- list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
- $v = (int)$v;
- if ( $v<1 )
- {
- fclose ( $fp );
- $this->_error = "expected searchd protocol version 1+, got version '$v'";
- return false;
- }
-
- 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, min ( 8192, $left ) );
- if ( $chunk )
- {
- $response .= $chunk;
- $left -= strlen($chunk);
- }
- }
- }
- if ( $this->_socket === false )
- 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, $rankexpr="" )
- {
- assert ( $ranker>=0 && $ranker<SPH_RANK_TOTAL );
- assert ( is_string($rankexpr) );
- $this->_ranker = $ranker;
- $this->_rankexpr = $rankexpr;
- }
-
- /// 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_numeric($min) );
- assert ( is_numeric($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;
- }
-
- /// set attribute values override
- /// there can be only one override per attribute
- /// $values must be a hash that maps document IDs to attribute values
- function SetOverride ( $attrname, $attrtype, $values )
- {
- assert ( is_string ( $attrname ) );
- assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
- assert ( is_array ( $values ) );
-
- $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
- }
-
- /// set select-list (attributes or expressions), SQL-like syntax
- function SetSelect ( $select )
- {
- assert ( is_string ( $select ) );
- $this->_select = $select;
- }
-
- //////////////////////////////////////////////////////////////////////////////
-
- /// 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= "";
- }
-
- /// clear all attribute value overrides (for multi-queries)
- function ResetOverrides ()
- {
- $this->_overrides = array ();
- }
-
- //////////////////////////////////////////////////////////////////////////////
-
- /// 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 ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker );
- if ( $this->_ranker==SPH_RANK_EXPR )
- $req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr;
- $req .= pack ( "N", $this->_sort ); // (deprecated) sort mode
- $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 .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $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 .= sphPackI64 ( $value );
- break;
-
- case SPH_FILTER_RANGE:
- $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $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;
-
- // attribute overrides
- $req .= pack ( "N", count($this->_overrides) );
- foreach ( $this->_overrides as $key => $entry )
- {
- $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
- $req .= pack ( "NN", $entry["type"], count($entry["values"]) );
- foreach ( $entry["values"] as $id=>$val )
- {
- assert ( is_numeric($id) );
- assert ( is_numeric($val) );
-
- $req .= sphPackU64 ( $id );
- switch ( $entry["type"] )
- {
- case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break;
- case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break;
- default: $req .= pack ( "N", $val ); break;
- }
- }
- }
-
- // select-list
- $req .= pack ( "N", strlen($this->_select) ) . $this->_select;
-
- // 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 = 8+strlen($req);
- $req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header
-
- if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
- !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
- {
- $this->_MBPop ();
- return false;
- }
-
- // query sent ok; we can reset reqs now
- $this->_reqs = array ();
-
- // parse and return response
- return $this->_ParseSearchResponse ( $response, $nreqs );
- }
-
- /// parse and return search query (or queries) response
- function _ParseSearchResponse ( $response, $nreqs )
- {
- $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 = sphUnpackU64 ( 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;
- $doc = sphFixUint($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 64bit ints
- if ( $type==SPH_ATTR_BIGINT )
- {
- $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
- continue;
- }
-
- // 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][] = sphFixUint($val);
- }
- } else if ( $type==SPH_ATTR_MULTI64 )
- {
- $attrvals[$attr] = array ();
- $nvalues = $val;
- while ( $nvalues>0 && $p<$max )
- {
- $attrvals[$attr][] = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
- $nvalues -= 2;
- }
- } else if ( $type==SPH_ATTR_STRING )
- {
- $attrvals[$attr] = substr ( $response, $p, $val );
- $p += $val;
- } else
- {
- $attrvals[$attr] = sphFixUint($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"] = "<b>";
- if ( !isset($opts["after_match"]) ) $opts["after_match"] = "</b>";
- if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... ";
- if ( !isset($opts["limit"]) ) $opts["limit"] = 256;
- if ( !isset($opts["limit_passages"]) ) $opts["limit_passages"] = 0;
- if ( !isset($opts["limit_words"]) ) $opts["limit_words"] = 0;
- 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;
- if ( !isset($opts["query_mode"]) ) $opts["query_mode"] = false;
- if ( !isset($opts["force_all_words"]) ) $opts["force_all_words"] = false;
- if ( !isset($opts["start_passage_id"]) ) $opts["start_passage_id"] = 1;
- if ( !isset($opts["load_files"]) ) $opts["load_files"] = false;
- if ( !isset($opts["html_strip_mode"]) ) $opts["html_strip_mode"] = "index";
- if ( !isset($opts["allow_empty"]) ) $opts["allow_empty"] = false;
- if ( !isset($opts["passage_boundary"]) ) $opts["passage_boundary"] = "none";
- if ( !isset($opts["emit_zones"]) ) $opts["emit_zones"] = false;
- if ( !isset($opts["load_files_scattered"]) ) $opts["load_files_scattered"] = false;
-
-
- /////////////////
- // build request
- /////////////////
-
- // v.1.2 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;
- if ( $opts["query_mode"] ) $flags |= 32;
- if ( $opts["force_all_words"] ) $flags |= 64;
- if ( $opts["load_files"] ) $flags |= 128;
- if ( $opts["allow_empty"] ) $flags |= 256;
- if ( $opts["emit_zones"] ) $flags |= 512;
- if ( $opts["load_files_scattered"] ) $flags |= 1024;
- $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 ( "NN", (int)$opts["limit"], (int)$opts["around"] );
- $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2
- $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"];
- $req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"];
-
- // 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
- if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
- !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
- {
- $this->_MBPop ();
- return false;
- }
-
- //////////////////
- // parse response
- //////////////////
-
- $pos = 0;
- $res = array ();
- $rlen = strlen($response);
- for ( $i=0; $i<count($docs); $i++ )
- {
- list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
- $pos += 4;
-
- if ( $pos+$len > $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
- if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
- !( $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
- /////////////////////////////////////////////////////////////////////////////
-
- /// batch update given attributes in given rows in given indexes
- /// returns amount of updated documents (0 or more) on success, or -1 on failure
- function UpdateAttributes ( $index, $attrs, $values, $mva=false )
- {
- // verify everything
- assert ( is_string($index) );
- assert ( is_bool($mva) );
-
- 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 )
- {
- if ( $mva )
- {
- assert ( is_array($v) );
- foreach ( $v as $vv )
- assert ( is_int($vv) );
- } else
- assert ( is_int($v) );
- }
- }
-
- // build request
- $this->_MBPush ();
- $req = pack ( "N", strlen($index) ) . $index;
-
- $req .= pack ( "N", count($attrs) );
- foreach ( $attrs as $attr )
- {
- $req .= pack ( "N", strlen($attr) ) . $attr;
- $req .= pack ( "N", $mva ? 1 : 0 );
- }
-
- $req .= pack ( "N", count($values) );
- foreach ( $values as $id=>$entry )
- {
- $req .= sphPackU64 ( $id );
- foreach ( $entry as $v )
- {
- $req .= pack ( "N", $mva ? count($v) : $v );
- if ( $mva )
- foreach ( $v as $vv )
- $req .= pack ( "N", $vv );
- }
- }
-
- // 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
- if ( !$this->_Send ( $fp, $req, $len+8 ) )
- {
- $this->_MBPop ();
- return -1;
- }
-
- 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;
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // persistent connections
- /////////////////////////////////////////////////////////////////////////////
-
- function Open()
- {
- if ( $this->_socket !== false )
- {
- $this->_error = 'already connected';
- return false;
- }
- if ( !$fp = $this->_Connect() )
- return false;
-
- // command, command version = 0, body length = 4, body = 1
- $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
- if ( !$this->_Send ( $fp, $req, 12 ) )
- return false;
-
- $this->_socket = $fp;
- return true;
- }
-
- function Close()
- {
- if ( $this->_socket === false )
- {
- $this->_error = 'not connected';
- return false;
- }
-
- fclose ( $this->_socket );
- $this->_socket = false;
-
- return true;
- }
-
- //////////////////////////////////////////////////////////////////////////
- // status
- //////////////////////////////////////////////////////////////////////////
-
- function Status ()
- {
- $this->_MBPush ();
- if (!( $fp = $this->_Connect() ))
- {
- $this->_MBPop();
- return false;
- }
-
- $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1
- if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
- !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
- {
- $this->_MBPop ();
- return false;
- }
-
- $res = substr ( $response, 4 ); // just ignore length, error handling, etc
- $p = 0;
- list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
-
- $res = array();
- for ( $i=0; $i<$rows; $i++ )
- for ( $j=0; $j<$cols; $j++ )
- {
- list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- $res[$i][] = substr ( $response, $p, $len ); $p += $len;
- }
-
- $this->_MBPop ();
- return $res;
- }
-
- //////////////////////////////////////////////////////////////////////////
- // flush
- //////////////////////////////////////////////////////////////////////////
-
- function FlushAttributes ()
- {
- $this->_MBPush ();
- if (!( $fp = $this->_Connect() ))
- {
- $this->_MBPop();
- return -1;
- }
-
- $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0
- if ( !( $this->_Send ( $fp, $req, 8 ) ) ||
- !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) )
- {
- $this->_MBPop ();
- return -1;
- }
-
- $tag = -1;
- if ( strlen($response)==4 )
- list(,$tag) = unpack ( "N*", $response );
- else
- $this->_error = "unexpected response length";
-
- $this->_MBPop ();
- return $tag;
- }
-}
-
-//
-// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
-//
+<?php
+
+//
+// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
+//
+
+//
+// Copyright (c) 2001-2012, Andrew Aksyonoff
+// Copyright (c) 2008-2012, Sphinx Technologies Inc
+// All rights reserved
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License. You should have
+// received a copy of the GPL license along with this program; if you
+// did not, you can find it at http://www.gnu.org/
+//
+
+/////////////////////////////////////////////////////////////////////////////
+// PHP version of Sphinx searchd client (PHP API)
+/////////////////////////////////////////////////////////////////////////////
+
+/// known searchd commands
+define ( "SEARCHD_COMMAND_SEARCH", 0 );
+define ( "SEARCHD_COMMAND_EXCERPT", 1 );
+define ( "SEARCHD_COMMAND_UPDATE", 2 );
+define ( "SEARCHD_COMMAND_KEYWORDS", 3 );
+define ( "SEARCHD_COMMAND_PERSIST", 4 );
+define ( "SEARCHD_COMMAND_STATUS", 5 );
+define ( "SEARCHD_COMMAND_FLUSHATTRS", 7 );
+
+/// current client-side command implementation versions
+define ( "VER_COMMAND_SEARCH", 0x119 );
+define ( "VER_COMMAND_EXCERPT", 0x104 );
+define ( "VER_COMMAND_UPDATE", 0x102 );
+define ( "VER_COMMAND_KEYWORDS", 0x100 );
+define ( "VER_COMMAND_STATUS", 0x100 );
+define ( "VER_COMMAND_QUERY", 0x100 );
+define ( "VER_COMMAND_FLUSHATTRS", 0x100 );
+
+/// known searchd status codes
+define ( "SEARCHD_OK", 0 );
+define ( "SEARCHD_ERROR", 1 );
+define ( "SEARCHD_RETRY", 2 );
+define ( "SEARCHD_WARNING", 3 );
+
+/// known match modes
+define ( "SPH_MATCH_ALL", 0 );
+define ( "SPH_MATCH_ANY", 1 );
+define ( "SPH_MATCH_PHRASE", 2 );
+define ( "SPH_MATCH_BOOLEAN", 3 );
+define ( "SPH_MATCH_EXTENDED", 4 );
+define ( "SPH_MATCH_FULLSCAN", 5 );
+define ( "SPH_MATCH_EXTENDED2", 6 ); // extended engine V2 (TEMPORARY, WILL BE REMOVED)
+
+/// known ranking modes (ext2 only)
+define ( "SPH_RANK_PROXIMITY_BM25", 0 ); ///< default mode, phrase proximity major factor and BM25 minor one
+define ( "SPH_RANK_BM25", 1 ); ///< statistical mode, BM25 ranking only (faster but worse quality)
+define ( "SPH_RANK_NONE", 2 ); ///< no ranking, all matches get a weight of 1
+define ( "SPH_RANK_WORDCOUNT", 3 ); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
+define ( "SPH_RANK_PROXIMITY", 4 );
+define ( "SPH_RANK_MATCHANY", 5 );
+define ( "SPH_RANK_FIELDMASK", 6 );
+define ( "SPH_RANK_SPH04", 7 );
+define ( "SPH_RANK_EXPR", 8 );
+define ( "SPH_RANK_TOTAL", 9 );
+
+/// known sort modes
+define ( "SPH_SORT_RELEVANCE", 0 );
+define ( "SPH_SORT_ATTR_DESC", 1 );
+define ( "SPH_SORT_ATTR_ASC", 2 );
+define ( "SPH_SORT_TIME_SEGMENTS", 3 );
+define ( "SPH_SORT_EXTENDED", 4 );
+define ( "SPH_SORT_EXPR", 5 );
+
+/// known filter types
+define ( "SPH_FILTER_VALUES", 0 );
+define ( "SPH_FILTER_RANGE", 1 );
+define ( "SPH_FILTER_FLOATRANGE", 2 );
+
+/// known attribute types
+define ( "SPH_ATTR_INTEGER", 1 );
+define ( "SPH_ATTR_TIMESTAMP", 2 );
+define ( "SPH_ATTR_ORDINAL", 3 );
+define ( "SPH_ATTR_BOOL", 4 );
+define ( "SPH_ATTR_FLOAT", 5 );
+define ( "SPH_ATTR_BIGINT", 6 );
+define ( "SPH_ATTR_STRING", 7 );
+define ( "SPH_ATTR_MULTI", 0x40000001 );
+define ( "SPH_ATTR_MULTI64", 0x40000002 );
+
+/// known grouping functions
+define ( "SPH_GROUPBY_DAY", 0 );
+define ( "SPH_GROUPBY_WEEK", 1 );
+define ( "SPH_GROUPBY_MONTH", 2 );
+define ( "SPH_GROUPBY_YEAR", 3 );
+define ( "SPH_GROUPBY_ATTR", 4 );
+define ( "SPH_GROUPBY_ATTRPAIR", 5 );
+
+// important properties of PHP's integers:
+// - always signed (one bit short of PHP_INT_SIZE)
+// - conversion from string to int is saturated
+// - float is double
+// - div converts arguments to floats
+// - mod converts arguments to ints
+
+// the packing code below works as follows:
+// - when we got an int, just pack it
+// if performance is a problem, this is the branch users should aim for
+//
+// - otherwise, we got a number in string form
+// this might be due to different reasons, but we assume that this is
+// because it didn't fit into PHP int
+//
+// - factor the string into high and low ints for packing
+// - if we have bcmath, then it is used
+// - if we don't, we have to do it manually (this is the fun part)
+//
+// - x64 branch does factoring using ints
+// - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
+//
+// unpacking routines are pretty much the same.
+// - return ints if we can
+// - otherwise format number into a string
+
+/// pack 64-bit signed
+function sphPackI64 ( $v )
+{
+ assert ( is_numeric($v) );
+
+ // x64
+ if ( PHP_INT_SIZE>=8 )
+ {
+ $v = (int)$v;
+ return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
+ }
+
+ // x32, int
+ if ( is_int($v) )
+ return pack ( "NN", $v < 0 ? -1 : 0, $v );
+
+ // x32, bcmath
+ if ( function_exists("bcmul") )
+ {
+ if ( bccomp ( $v, 0 ) == -1 )
+ $v = bcadd ( "18446744073709551616", $v );
+ $h = bcdiv ( $v, "4294967296", 0 );
+ $l = bcmod ( $v, "4294967296" );
+ return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
+ }
+
+ // x32, no-bcmath
+ $p = max(0, strlen($v) - 13);
+ $lo = abs((float)substr($v, $p));
+ $hi = abs((float)substr($v, 0, $p));
+
+ $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
+ $q = floor($m/4294967296.0);
+ $l = $m - ($q*4294967296.0);
+ $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
+
+ if ( $v<0 )
+ {
+ if ( $l==0 )
+ $h = 4294967296.0 - $h;
+ else
+ {
+ $h = 4294967295.0 - $h;
+ $l = 4294967296.0 - $l;
+ }
+ }
+ return pack ( "NN", $h, $l );
+}
+
+/// pack 64-bit unsigned
+function sphPackU64 ( $v )
+{
+ assert ( is_numeric($v) );
+
+ // x64
+ if ( PHP_INT_SIZE>=8 )
+ {
+ assert ( $v>=0 );
+
+ // x64, int
+ if ( is_int($v) )
+ return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
+
+ // x64, bcmath
+ if ( function_exists("bcmul") )
+ {
+ $h = bcdiv ( $v, 4294967296, 0 );
+ $l = bcmod ( $v, 4294967296 );
+ return pack ( "NN", $h, $l );
+ }
+
+ // x64, no-bcmath
+ $p = max ( 0, strlen($v) - 13 );
+ $lo = (int)substr ( $v, $p );
+ $hi = (int)substr ( $v, 0, $p );
+
+ $m = $lo + $hi*1316134912;
+ $l = $m % 4294967296;
+ $h = $hi*2328 + (int)($m/4294967296);
+
+ return pack ( "NN", $h, $l );
+ }
+
+ // x32, int
+ if ( is_int($v) )
+ return pack ( "NN", 0, $v );
+
+ // x32, bcmath
+ if ( function_exists("bcmul") )
+ {
+ $h = bcdiv ( $v, "4294967296", 0 );
+ $l = bcmod ( $v, "4294967296" );
+ return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
+ }
+
+ // x32, no-bcmath
+ $p = max(0, strlen($v) - 13);
+ $lo = (float)substr($v, $p);
+ $hi = (float)substr($v, 0, $p);
+
+ $m = $lo + $hi*1316134912.0;
+ $q = floor($m / 4294967296.0);
+ $l = $m - ($q * 4294967296.0);
+ $h = $hi*2328.0 + $q;
+
+ return pack ( "NN", $h, $l );
+}
+
+// unpack 64-bit unsigned
+function sphUnpackU64 ( $v )
+{
+ list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
+
+ if ( PHP_INT_SIZE>=8 )
+ {
+ if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
+ if ( $lo<0 ) $lo += (1<<32);
+
+ // x64, int
+ if ( $hi<=2147483647 )
+ return ($hi<<32) + $lo;
+
+ // x64, bcmath
+ if ( function_exists("bcmul") )
+ return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
+
+ // x64, no-bcmath
+ $C = 100000;
+ $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
+ $l = (($hi % $C) << 32) + ($lo % $C);
+ if ( $l>$C )
+ {
+ $h += (int)($l / $C);
+ $l = $l % $C;
+ }
+
+ if ( $h==0 )
+ return $l;
+ return sprintf ( "%d%05d", $h, $l );
+ }
+
+ // x32, int
+ if ( $hi==0 )
+ {
+ if ( $lo>0 )
+ return $lo;
+ return sprintf ( "%u", $lo );
+ }
+
+ $hi = sprintf ( "%u", $hi );
+ $lo = sprintf ( "%u", $lo );
+
+ // x32, bcmath
+ if ( function_exists("bcmul") )
+ return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
+
+ // x32, no-bcmath
+ $hi = (float)$hi;
+ $lo = (float)$lo;
+
+ $q = floor($hi/10000000.0);
+ $r = $hi - $q*10000000.0;
+ $m = $lo + $r*4967296.0;
+ $mq = floor($m/10000000.0);
+ $l = $m - $mq*10000000.0;
+ $h = $q*4294967296.0 + $r*429.0 + $mq;
+
+ $h = sprintf ( "%.0f", $h );
+ $l = sprintf ( "%07.0f", $l );
+ if ( $h=="0" )
+ return sprintf( "%.0f", (float)$l );
+ return $h . $l;
+}
+
+// unpack 64-bit signed
+function sphUnpackI64 ( $v )
+{
+ list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
+
+ // x64
+ if ( PHP_INT_SIZE>=8 )
+ {
+ if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
+ if ( $lo<0 ) $lo += (1<<32);
+
+ return ($hi<<32) + $lo;
+ }
+
+ // x32, int
+ if ( $hi==0 )
+ {
+ if ( $lo>0 )
+ return $lo;
+ return sprintf ( "%u", $lo );
+ }
+ // x32, int
+ elseif ( $hi==-1 )
+ {
+ if ( $lo<0 )
+ return $lo;
+ return sprintf ( "%.0f", $lo - 4294967296.0 );
+ }
+
+ $neg = "";
+ $c = 0;
+ if ( $hi<0 )
+ {
+ $hi = ~$hi;
+ $lo = ~$lo;
+ $c = 1;
+ $neg = "-";
+ }
+
+ $hi = sprintf ( "%u", $hi );
+ $lo = sprintf ( "%u", $lo );
+
+ // x32, bcmath
+ if ( function_exists("bcmul") )
+ return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
+
+ // x32, no-bcmath
+ $hi = (float)$hi;
+ $lo = (float)$lo;
+
+ $q = floor($hi/10000000.0);
+ $r = $hi - $q*10000000.0;
+ $m = $lo + $r*4967296.0;
+ $mq = floor($m/10000000.0);
+ $l = $m - $mq*10000000.0 + $c;
+ $h = $q*4294967296.0 + $r*429.0 + $mq;
+ if ( $l==10000000 )
+ {
+ $l = 0;
+ $h += 1;
+ }
+
+ $h = sprintf ( "%.0f", $h );
+ $l = sprintf ( "%07.0f", $l );
+ if ( $h=="0" )
+ return $neg . sprintf( "%.0f", (float)$l );
+ return $neg . $h . $l;
+}
+
+
+function sphFixUint ( $value )
+{
+ if ( PHP_INT_SIZE>=8 )
+ {
+ // x64 route, workaround broken unpack() in 5.2.2+
+ if ( $value<0 ) $value += (1<<32);
+ return $value;
+ }
+ else
+ {
+ // x32 route, workaround php signed/unsigned braindamage
+ return sprintf ( "%u", $value );
+ }
+}
+
+
+/// sphinx searchd client class
+class SphinxClient
+{
+ var $_host; ///< searchd host (default is "localhost")
+ var $_port; ///< searchd port (default is 9312)
+ 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 $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR)
+ var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
+ var $_fieldweights; ///< per-field-name weights
+ var $_overrides; ///< per-query attribute values overrides
+ var $_select; ///< select-list (attributes or expressions, with optional aliases)
+
+ var $_error; ///< last error message
+ var $_warning; ///< last warning message
+ var $_connerror; ///< connection error vs remote error flag
+
+ 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 = 9312;
+ $this->_path = false;
+ $this->_socket = false;
+
+ // 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->_rankexpr = "";
+ $this->_maxquerytime= 0;
+ $this->_fieldweights= array();
+ $this->_overrides = array();
+ $this->_select = "*";
+
+ $this->_error = ""; // per-reply fields (for single-query case)
+ $this->_warning = "";
+ $this->_connerror = false;
+
+ $this->_reqs = array (); // requests storage (for multi-query case)
+ $this->_mbenc = "";
+ $this->_arrayresult = false;
+ $this->_timeout = 0;
+ }
+
+ function __destruct()
+ {
+ if ( $this->_socket !== false )
+ fclose ( $this->_socket );
+ }
+
+ /// get last error message (string)
+ function GetLastError ()
+ {
+ return $this->_error;
+ }
+
+ /// get last warning message (string)
+ function GetLastWarning ()
+ {
+ return $this->_warning;
+ }
+
+ /// get last error flag (to tell network connection errors from searchd errors or broken responses)
+ function IsConnectError()
+ {
+ return $this->_connerror;
+ }
+
+ /// set searchd host name (string) and port (integer)
+ function SetServer ( $host, $port = 0 )
+ {
+ assert ( is_string($host) );
+ if ( $host[0] == '/')
+ {
+ $this->_path = 'unix://' . $host;
+ return;
+ }
+ if ( substr ( $host, 0, 7 )=="unix://" )
+ {
+ $this->_path = $host;
+ return;
+ }
+
+ assert ( is_int($port) );
+ $this->_host = $host;
+ $this->_port = $port;
+ $this->_path = '';
+
+ }
+
+ /// set server connection timeout (0 to remove)
+ function SetConnectTimeout ( $timeout )
+ {
+ assert ( is_numeric($timeout) );
+ $this->_timeout = $timeout;
+ }
+
+
+ function _Send ( $handle, $data, $length )
+ {
+ if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
+ {
+ $this->_error = 'connection unexpectedly closed (timed out?)';
+ $this->_connerror = true;
+ return false;
+ }
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+
+ /// 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 ()
+ {
+ if ( $this->_socket!==false )
+ {
+ // we are in persistent connection mode, so we have a socket
+ // however, need to check whether it's still alive
+ if ( !@feof ( $this->_socket ) )
+ return $this->_socket;
+
+ // force reopen
+ $this->_socket = false;
+ }
+
+ $errno = 0;
+ $errstr = "";
+ $this->_connerror = false;
+
+ if ( $this->_path )
+ {
+ $host = $this->_path;
+ $port = 0;
+ }
+ else
+ {
+ $host = $this->_host;
+ $port = $this->_port;
+ }
+
+ if ( $this->_timeout<=0 )
+ $fp = @fsockopen ( $host, $port, $errno, $errstr );
+ else
+ $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
+
+ if ( !$fp )
+ {
+ if ( $this->_path )
+ $location = $this->_path;
+ else
+ $location = "{$this->_host}:{$this->_port}";
+
+ $errstr = trim ( $errstr );
+ $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
+ $this->_connerror = true;
+ return false;
+ }
+
+ // send my version
+ // this is a subtle part. we must do it before (!) reading back from searchd.
+ // because otherwise under some conditions (reported on FreeBSD for instance)
+ // TCP stack could throttle write-write-read pattern because of Nagle.
+ if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
+ {
+ fclose ( $fp );
+ $this->_error = "failed to send client protocol version";
+ return false;
+ }
+
+ // check version
+ list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
+ $v = (int)$v;
+ if ( $v<1 )
+ {
+ fclose ( $fp );
+ $this->_error = "expected searchd protocol version 1+, got version '$v'";
+ return false;
+ }
+
+ 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, min ( 8192, $left ) );
+ if ( $chunk )
+ {
+ $response .= $chunk;
+ $left -= strlen($chunk);
+ }
+ }
+ }
+ if ( $this->_socket === false )
+ 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, $rankexpr="" )
+ {
+ assert ( $ranker>=0 && $ranker<SPH_RANK_TOTAL );
+ assert ( is_string($rankexpr) );
+ $this->_ranker = $ranker;
+ $this->_rankexpr = $rankexpr;
+ }
+
+ /// 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_numeric($min) );
+ assert ( is_numeric($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;
+ }
+
+ /// set attribute values override
+ /// there can be only one override per attribute
+ /// $values must be a hash that maps document IDs to attribute values
+ function SetOverride ( $attrname, $attrtype, $values )
+ {
+ assert ( is_string ( $attrname ) );
+ assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
+ assert ( is_array ( $values ) );
+
+ $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
+ }
+
+ /// set select-list (attributes or expressions), SQL-like syntax
+ function SetSelect ( $select )
+ {
+ assert ( is_string ( $select ) );
+ $this->_select = $select;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ /// 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= "";
+ }
+
+ /// clear all attribute value overrides (for multi-queries)
+ function ResetOverrides ()
+ {
+ $this->_overrides = array ();
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ /// 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 ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker );
+ if ( $this->_ranker==SPH_RANK_EXPR )
+ $req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr;
+ $req .= pack ( "N", $this->_sort ); // (deprecated) sort mode
+ $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 .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $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 .= sphPackI64 ( $value );
+ break;
+
+ case SPH_FILTER_RANGE:
+ $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $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;
+
+ // attribute overrides
+ $req .= pack ( "N", count($this->_overrides) );
+ foreach ( $this->_overrides as $key => $entry )
+ {
+ $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
+ $req .= pack ( "NN", $entry["type"], count($entry["values"]) );
+ foreach ( $entry["values"] as $id=>$val )
+ {
+ assert ( is_numeric($id) );
+ assert ( is_numeric($val) );
+
+ $req .= sphPackU64 ( $id );
+ switch ( $entry["type"] )
+ {
+ case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break;
+ case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break;
+ default: $req .= pack ( "N", $val ); break;
+ }
+ }
+ }
+
+ // select-list
+ $req .= pack ( "N", strlen($this->_select) ) . $this->_select;
+
+ // 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 = 8+strlen($req);
+ $req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header
+
+ if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
+ {
+ $this->_MBPop ();
+ return false;
+ }
+
+ // query sent ok; we can reset reqs now
+ $this->_reqs = array ();
+
+ // parse and return response
+ return $this->_ParseSearchResponse ( $response, $nreqs );
+ }
+
+ /// parse and return search query (or queries) response
+ function _ParseSearchResponse ( $response, $nreqs )
+ {
+ $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 = sphUnpackU64 ( 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;
+ $doc = sphFixUint($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 64bit ints
+ if ( $type==SPH_ATTR_BIGINT )
+ {
+ $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
+ continue;
+ }
+
+ // 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][] = sphFixUint($val);
+ }
+ } else if ( $type==SPH_ATTR_MULTI64 )
+ {
+ $attrvals[$attr] = array ();
+ $nvalues = $val;
+ while ( $nvalues>0 && $p<$max )
+ {
+ $attrvals[$attr][] = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
+ $nvalues -= 2;
+ }
+ } else if ( $type==SPH_ATTR_STRING )
+ {
+ $attrvals[$attr] = substr ( $response, $p, $val );
+ $p += $val;
+ } else
+ {
+ $attrvals[$attr] = sphFixUint($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"] = "<b>";
+ if ( !isset($opts["after_match"]) ) $opts["after_match"] = "</b>";
+ if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... ";
+ if ( !isset($opts["limit"]) ) $opts["limit"] = 256;
+ if ( !isset($opts["limit_passages"]) ) $opts["limit_passages"] = 0;
+ if ( !isset($opts["limit_words"]) ) $opts["limit_words"] = 0;
+ 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;
+ if ( !isset($opts["query_mode"]) ) $opts["query_mode"] = false;
+ if ( !isset($opts["force_all_words"]) ) $opts["force_all_words"] = false;
+ if ( !isset($opts["start_passage_id"]) ) $opts["start_passage_id"] = 1;
+ if ( !isset($opts["load_files"]) ) $opts["load_files"] = false;
+ if ( !isset($opts["html_strip_mode"]) ) $opts["html_strip_mode"] = "index";
+ if ( !isset($opts["allow_empty"]) ) $opts["allow_empty"] = false;
+ if ( !isset($opts["passage_boundary"]) ) $opts["passage_boundary"] = "none";
+ if ( !isset($opts["emit_zones"]) ) $opts["emit_zones"] = false;
+ if ( !isset($opts["load_files_scattered"]) ) $opts["load_files_scattered"] = false;
+
+
+ /////////////////
+ // build request
+ /////////////////
+
+ // v.1.2 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;
+ if ( $opts["query_mode"] ) $flags |= 32;
+ if ( $opts["force_all_words"] ) $flags |= 64;
+ if ( $opts["load_files"] ) $flags |= 128;
+ if ( $opts["allow_empty"] ) $flags |= 256;
+ if ( $opts["emit_zones"] ) $flags |= 512;
+ if ( $opts["load_files_scattered"] ) $flags |= 1024;
+ $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 ( "NN", (int)$opts["limit"], (int)$opts["around"] );
+ $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2
+ $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"];
+ $req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"];
+
+ // 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
+ if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
+ {
+ $this->_MBPop ();
+ return false;
+ }
+
+ //////////////////
+ // parse response
+ //////////////////
+
+ $pos = 0;
+ $res = array ();
+ $rlen = strlen($response);
+ for ( $i=0; $i<count($docs); $i++ )
+ {
+ list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
+ $pos += 4;
+
+ if ( $pos+$len > $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
+ if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
+ !( $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
+ /////////////////////////////////////////////////////////////////////////////
+
+ /// batch update given attributes in given rows in given indexes
+ /// returns amount of updated documents (0 or more) on success, or -1 on failure
+ function UpdateAttributes ( $index, $attrs, $values, $mva=false )
+ {
+ // verify everything
+ assert ( is_string($index) );
+ assert ( is_bool($mva) );
+
+ 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 )
+ {
+ if ( $mva )
+ {
+ assert ( is_array($v) );
+ foreach ( $v as $vv )
+ assert ( is_int($vv) );
+ } else
+ assert ( is_int($v) );
+ }
+ }
+
+ // build request
+ $this->_MBPush ();
+ $req = pack ( "N", strlen($index) ) . $index;
+
+ $req .= pack ( "N", count($attrs) );
+ foreach ( $attrs as $attr )
+ {
+ $req .= pack ( "N", strlen($attr) ) . $attr;
+ $req .= pack ( "N", $mva ? 1 : 0 );
+ }
+
+ $req .= pack ( "N", count($values) );
+ foreach ( $values as $id=>$entry )
+ {
+ $req .= sphPackU64 ( $id );
+ foreach ( $entry as $v )
+ {
+ $req .= pack ( "N", $mva ? count($v) : $v );
+ if ( $mva )
+ foreach ( $v as $vv )
+ $req .= pack ( "N", $vv );
+ }
+ }
+
+ // 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
+ if ( !$this->_Send ( $fp, $req, $len+8 ) )
+ {
+ $this->_MBPop ();
+ return -1;
+ }
+
+ 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;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // persistent connections
+ /////////////////////////////////////////////////////////////////////////////
+
+ function Open()
+ {
+ if ( $this->_socket !== false )
+ {
+ $this->_error = 'already connected';
+ return false;
+ }
+ if ( !$fp = $this->_Connect() )
+ return false;
+
+ // command, command version = 0, body length = 4, body = 1
+ $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
+ if ( !$this->_Send ( $fp, $req, 12 ) )
+ return false;
+
+ $this->_socket = $fp;
+ return true;
+ }
+
+ function Close()
+ {
+ if ( $this->_socket === false )
+ {
+ $this->_error = 'not connected';
+ return false;
+ }
+
+ fclose ( $this->_socket );
+ $this->_socket = false;
+
+ return true;
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // status
+ //////////////////////////////////////////////////////////////////////////
+
+ function Status ()
+ {
+ $this->_MBPush ();
+ if (!( $fp = $this->_Connect() ))
+ {
+ $this->_MBPop();
+ return false;
+ }
+
+ $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1
+ if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
+ {
+ $this->_MBPop ();
+ return false;
+ }
+
+ $res = substr ( $response, 4 ); // just ignore length, error handling, etc
+ $p = 0;
+ list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
+
+ $res = array();
+ for ( $i=0; $i<$rows; $i++ )
+ for ( $j=0; $j<$cols; $j++ )
+ {
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+ $res[$i][] = substr ( $response, $p, $len ); $p += $len;
+ }
+
+ $this->_MBPop ();
+ return $res;
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // flush
+ //////////////////////////////////////////////////////////////////////////
+
+ function FlushAttributes ()
+ {
+ $this->_MBPush ();
+ if (!( $fp = $this->_Connect() ))
+ {
+ $this->_MBPop();
+ return -1;
+ }
+
+ $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0
+ if ( !( $this->_Send ( $fp, $req, 8 ) ) ||
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) )
+ {
+ $this->_MBPop ();
+ return -1;
+ }
+
+ $tag = -1;
+ if ( strlen($response)==4 )
+ list(,$tag) = unpack ( "N*", $response );
+ else
+ $this->_error = "unexpected response length";
+
+ $this->_MBPop ();
+ return $tag;
+ }
+}
+
+//
+// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
+//
diff --git a/phpBB/includes/style/extension_path_provider.php b/phpBB/includes/style/extension_path_provider.php
index 4eac300424..6976a45ed0 100644
--- a/phpBB/includes/style/extension_path_provider.php
+++ b/phpBB/includes/style/extension_path_provider.php
@@ -92,7 +92,7 @@ class phpbb_style_extension_path_provider extends phpbb_extension_provider imple
if ($path && !phpbb_is_absolute($path))
{
$result = $finder->directory('/' . $this->ext_dir_prefix . $path)
- ->get_directories(true, true);
+ ->get_directories(true, false, true);
foreach ($result as $ext => $ext_path)
{
$directories[$ext][] = $ext_path;
diff --git a/phpBB/includes/ucp/info/ucp_notifications.php b/phpBB/includes/ucp/info/ucp_notifications.php
new file mode 100644
index 0000000000..98d8b9db61
--- /dev/null
+++ b/phpBB/includes/ucp/info/ucp_notifications.php
@@ -0,0 +1,35 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @package module_install
+*/
+class ucp_notifications_info
+{
+ function module()
+ {
+ return array(
+ 'filename' => 'ucp_notifications',
+ 'title' => 'UCP_NOTIFICATION_OPTIONS',
+ 'version' => '1.0.0',
+ 'modes' => array(
+ 'notification_options' => array('title' => 'UCP_NOTIFICATION_OPTIONS', 'auth' => '', 'cat' => array('UCP_PREFS')),
+ 'notification_list' => array('title' => 'UCP_NOTIFICATION_LIST', 'auth' => '', 'cat' => array('UCP_MAIN')),
+ ),
+ );
+ }
+
+ function install()
+ {
+ }
+
+ function uninstall()
+ {
+ }
+}
diff --git a/phpBB/includes/ucp/ucp_activate.php b/phpBB/includes/ucp/ucp_activate.php
index a0d0baf10f..577761dfde 100644
--- a/phpBB/includes/ucp/ucp_activate.php
+++ b/phpBB/includes/ucp/ucp_activate.php
@@ -50,7 +50,7 @@ class ucp_activate
trigger_error('ALREADY_ACTIVATED');
}
- if (($user_row['user_inactive_reason'] == INACTIVE_MANUAL) || $user_row['user_actkey'] != $key)
+ if ($user_row['user_inactive_reason'] == INACTIVE_MANUAL || $user_row['user_actkey'] !== $key)
{
trigger_error('WRONG_ACTIVATION');
}
diff --git a/phpBB/includes/ucp/ucp_groups.php b/phpBB/includes/ucp/ucp_groups.php
index 9652986cf2..b9a06bc3b4 100644
--- a/phpBB/includes/ucp/ucp_groups.php
+++ b/phpBB/includes/ucp/ucp_groups.php
@@ -25,7 +25,7 @@ class ucp_groups
function main($id, $mode)
{
- global $config, $phpbb_root_path, $phpEx;
+ global $config, $phpbb_root_path, $phpEx, $phpbb_admin_path;
global $db, $user, $auth, $cache, $template;
global $request;
@@ -438,7 +438,7 @@ class ucp_groups
$group_name = $group_row['group_name'];
$group_type = $group_row['group_type'];
- $avatar_img = (!empty($group_row['group_avatar'])) ? get_user_avatar($group_row['group_avatar'], $group_row['group_avatar_type'], $group_row['group_avatar_width'], $group_row['group_avatar_height'], 'GROUP_AVATAR') : '<img src="' . $phpbb_root_path . 'adm/images/no_avatar.gif" alt="" />';
+ $avatar_img = (!empty($group_row['group_avatar'])) ? get_user_avatar($group_row['group_avatar'], $group_row['group_avatar_type'], $group_row['group_avatar_width'], $group_row['group_avatar_height'], 'GROUP_AVATAR') : '<img src="' . $phpbb_admin_path . 'images/no_avatar.gif" alt="" />';
$template->assign_vars(array(
'GROUP_NAME' => ($group_type == GROUP_SPECIAL) ? $user->lang['G_' . $group_name] : $group_name,
@@ -618,7 +618,7 @@ class ucp_groups
foreach ($test_variables as $test => $type)
{
- if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test]))
+ if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || isset($group_attributes['group_avatar']) && strpos($test, 'avatar') === 0))
{
settype($submit_ary[$test], $type);
$group_attributes['group_' . $test] = $group_row['group_' . $test] = $submit_ary[$test];
@@ -730,7 +730,7 @@ class ucp_groups
'GROUP_CLOSED' => $type_closed,
'GROUP_HIDDEN' => $type_hidden,
- 'U_SWATCH' => append_sid("{$phpbb_root_path}adm/swatch.$phpEx", 'form=ucp&amp;name=group_colour'),
+ 'U_SWATCH' => append_sid("{$phpbb_admin_path}swatch.$phpEx", 'form=ucp&amp;name=group_colour'),
'S_UCP_ACTION' => $this->u_action . "&amp;action=$action&amp;g=$group_id",
'L_AVATAR_EXPLAIN' => phpbb_avatar_explanation_string(),
));
diff --git a/phpBB/includes/ucp/ucp_notifications.php b/phpBB/includes/ucp/ucp_notifications.php
new file mode 100644
index 0000000000..338c921e94
--- /dev/null
+++ b/phpBB/includes/ucp/ucp_notifications.php
@@ -0,0 +1,226 @@
+<?php
+/**
+*
+* @package notifications
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+class ucp_notifications
+{
+ public $u_action;
+
+ public function main($id, $mode)
+ {
+ global $config, $template, $user, $request, $phpbb_container;
+ global $phpbb_root_path, $phpEx;
+
+ add_form_key('ucp_notification');
+
+ $start = $request->variable('start', 0);
+ $form_time = min($request->variable('form_time', 0), time());
+
+ $phpbb_notifications = $phpbb_container->get('notification_manager');
+
+ switch ($mode)
+ {
+ case 'notification_options':
+ $subscriptions = $phpbb_notifications->get_global_subscriptions(false);
+
+ // Add/remove subscriptions
+ if ($request->is_set_post('submit'))
+ {
+ if (!check_form_key('ucp_notification'))
+ {
+ trigger_error('FORM_INVALID');
+ }
+
+ $notification_methods = $phpbb_notifications->get_subscription_methods();
+
+ foreach($phpbb_notifications->get_subscription_types() as $group => $subscription_types)
+ {
+ foreach($subscription_types as $type => $data)
+ {
+ foreach($notification_methods as $method => $method_data)
+ {
+ if ($request->is_set_post($type . '_' . $method_data['id']) && (!isset($subscriptions[$type]) || !in_array($method_data['id'], $subscriptions[$type])))
+ {
+ $phpbb_notifications->add_subscription($type, 0, $method_data['id']);
+ }
+ else if (!$request->is_set_post($type . '_' . $method_data['id']) && isset($subscriptions[$type]) && in_array($method_data['id'], $subscriptions[$type]))
+ {
+ $phpbb_notifications->delete_subscription($type, 0, $method_data['id']);
+ }
+ }
+
+ if ($request->is_set_post($type . '_notification') && !isset($subscriptions[$type]))
+ {
+ $phpbb_notifications->add_subscription($type);
+ }
+ else if (!$request->is_set_post($type . '_notification') && isset($subscriptions[$type]))
+ {
+ $phpbb_notifications->delete_subscription($type);
+ }
+ }
+ }
+
+ meta_refresh(3, $this->u_action);
+ $message = $user->lang['PREFERENCES_UPDATED'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>');
+ trigger_error($message);
+ }
+
+ $this->output_notification_methods('notification_methods', $phpbb_notifications, $template, $user);
+
+ $this->output_notification_types($subscriptions, 'notification_types', $phpbb_notifications, $template, $user);
+
+ $this->tpl_name = 'ucp_notifications';
+ $this->page_title = 'UCP_NOTIFICATION_OPTIONS';
+ break;
+
+ case 'notification_list':
+ default:
+ // Mark all items read
+ if ($request->variable('mark', '') == 'all' && (confirm_box(true) || check_link_hash($request->variable('token', ''), 'mark_all_notifications_read')))
+ {
+ if (confirm_box(true))
+ {
+ $phpbb_notifications->mark_notifications_read(false, false, $user->data['user_id'], $form_time);
+
+ meta_refresh(3, $this->u_action);
+ $message = $user->lang['NOTIFICATIONS_MARK_ALL_READ_SUCCESS'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>');
+ trigger_error($message);
+ }
+ else
+ {
+ confirm_box(false, 'NOTIFICATIONS_MARK_ALL_READ', build_hidden_fields(array(
+ 'mark' => 'all',
+ 'form_time' => $form_time,
+ )));
+ }
+ }
+
+ // Mark specific notifications read
+ if ($request->is_set_post('submit'))
+ {
+ if (!check_form_key('ucp_notification'))
+ {
+ trigger_error('FORM_INVALID');
+ }
+
+ $mark_read = $request->variable('mark', array(0));
+
+ if (!empty($mark_read))
+ {
+ $phpbb_notifications->mark_notifications_read_by_id($mark_read, $form_time);
+ }
+ }
+
+ $notifications = $phpbb_notifications->load_notifications(array(
+ 'start' => $start,
+ 'limit' => $config['topics_per_page'],
+ 'count_total' => true,
+ ));
+
+ foreach ($notifications['notifications'] as $notification)
+ {
+ $template->assign_block_vars('notification_list', $notification->prepare_for_display());
+ }
+
+ $base_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=ucp_notifications&amp;mode=notification_list");
+ phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $notifications['total_count'], $config['topics_per_page'], $start);
+
+ $template->assign_vars(array(
+ 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $notifications['total_count'], $config['topics_per_page'], $start),
+ 'TOTAL_COUNT' => $user->lang('NOTIFICATIONS_COUNT', $notifications['total_count']),
+ 'U_MARK_ALL' => $base_url . '&amp;mark=all&amp;token=' . generate_link_hash('mark_all_notifications_read'),
+ ));
+
+ $this->tpl_name = 'ucp_notifications';
+ $this->page_title = 'UCP_NOTIFICATION_LIST';
+ break;
+ }
+
+ $template->assign_vars(array(
+ 'TITLE' => $user->lang($this->page_title),
+ 'TITLE_EXPLAIN' => $user->lang($this->page_title . '_EXPLAIN'),
+
+ 'MODE' => $mode,
+
+ 'FORM_TIME' => time(),
+ ));
+ }
+
+ /**
+ * Output all the notification types to the template
+ *
+ * @param string $block
+ * @param phpbb_notification_manager $phpbb_notifications
+ * @param phpbb_template $template
+ * @param phpbb_user $user
+ */
+ public function output_notification_types($subscriptions, $block = 'notification_types', phpbb_notification_manager $phpbb_notifications, phpbb_template $template, phpbb_user $user)
+ {
+ $notification_methods = $phpbb_notifications->get_subscription_methods();
+
+ foreach($phpbb_notifications->get_subscription_types() as $group => $subscription_types)
+ {
+ $template->assign_block_vars($block, array(
+ 'GROUP_NAME' => $user->lang($group),
+ ));
+
+ foreach($subscription_types as $type => $data)
+ {
+ $template->assign_block_vars($block, array(
+ 'TYPE' => $type,
+
+ 'NAME' => $user->lang($data['lang']),
+ 'EXPLAIN' => (isset($user->lang[$data['lang'] . '_EXPLAIN'])) ? $user->lang($data['lang'] . '_EXPLAIN') : '',
+
+ 'SUBSCRIBED' => (isset($subscriptions[$type])) ? true : false,
+ ));
+
+ foreach($notification_methods as $method => $method_data)
+ {
+ $template->assign_block_vars($block . '.notification_methods', array(
+ 'METHOD' => $method_data['id'],
+
+ 'NAME' => $user->lang($method_data['lang']),
+
+ 'SUBSCRIBED' => (isset($subscriptions[$type]) && in_array($method_data['id'], $subscriptions[$type])) ? true : false,
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Output all the notification methods to the template
+ *
+ * @param string $block
+ * @param phpbb_notification_manager $phpbb_notifications
+ * @param phpbb_template $template
+ * @param phpbb_user $user
+ */
+ public function output_notification_methods($block = 'notification_methods', phpbb_notification_manager $phpbb_notifications, phpbb_template $template, phpbb_user $user)
+ {
+ $notification_methods = $phpbb_notifications->get_subscription_methods();
+
+ foreach($notification_methods as $method => $method_data)
+ {
+ $template->assign_block_vars($block, array(
+ 'METHOD' => $method_data['id'],
+
+ 'NAME' => $user->lang($method_data['lang']),
+ ));
+ }
+ }
+}
diff --git a/phpBB/includes/ucp/ucp_pm_compose.php b/phpBB/includes/ucp/ucp_pm_compose.php
index 5577e8dab3..c2d12d17c2 100644
--- a/phpBB/includes/ucp/ucp_pm_compose.php
+++ b/phpBB/includes/ucp/ucp_pm_compose.php
@@ -353,7 +353,7 @@ function compose_pm($id, $mode, $action, $user_folders = array())
$message_attachment = 0;
$message_text = $message_subject = '';
- if ($to_user_id && $action == 'post')
+ if ($to_user_id && $to_user_id != ANONYMOUS && $action == 'post')
{
$address_list['u'][$to_user_id] = 'to';
}
diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php
index 23892c2c8c..7c3286c1d1 100644
--- a/phpBB/includes/ucp/ucp_prefs.php
+++ b/phpBB/includes/ucp/ucp_prefs.php
@@ -46,8 +46,6 @@ class ucp_prefs
'viewemail' => request_var('viewemail', (bool) $user->data['user_allow_viewemail']),
'massemail' => request_var('massemail', (bool) $user->data['user_allow_massemail']),
'hideonline' => request_var('hideonline', (bool) !$user->data['user_allow_viewonline']),
- 'notifypm' => request_var('notifypm', (bool) $user->data['user_notify_pm']),
- 'popuppm' => request_var('popuppm', (bool) $user->optionget('popuppm')),
'allowpm' => request_var('allowpm', (bool) $user->data['user_allow_pm']),
);
@@ -81,15 +79,12 @@ class ucp_prefs
if (!sizeof($error))
{
- $user->optionset('popuppm', $data['popuppm']);
-
$sql_ary = array(
'user_allow_pm' => $data['allowpm'],
'user_allow_viewemail' => $data['viewemail'],
'user_allow_massemail' => $data['massemail'],
'user_allow_viewonline' => ($auth->acl_get('u_hideonline')) ? !$data['hideonline'] : $user->data['user_allow_viewonline'],
'user_notify_type' => $data['notifymethod'],
- 'user_notify_pm' => $data['notifypm'],
'user_options' => $user->data['user_options'],
'user_dateformat' => $data['dateformat'],
@@ -172,8 +167,6 @@ class ucp_prefs
'S_MASS_EMAIL' => $data['massemail'],
'S_ALLOW_PM' => $data['allowpm'],
'S_HIDE_ONLINE' => $data['hideonline'],
- 'S_NOTIFY_PM' => $data['notifypm'],
- 'S_POPUP_PM' => $data['popuppm'],
'DATE_FORMAT' => $data['dateformat'],
'A_DATE_FORMAT' => addslashes($data['dateformat']),
diff --git a/phpBB/includes/user_loader.php b/phpBB/includes/user_loader.php
new file mode 100644
index 0000000000..77128d6570
--- /dev/null
+++ b/phpBB/includes/user_loader.php
@@ -0,0 +1,231 @@
+<?php
+/**
+*
+* @package phpBB3
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* User loader class
+*
+* This handles loading users from the database and
+* storing in them in a temporary cache so we do not
+* have to query the same user multiple times in
+* different services.
+*/
+class phpbb_user_loader
+{
+ /** @var phpbb_db_driver */
+ protected $db = null;
+
+ /** @var string */
+ protected $phpbb_root_path = null;
+
+ /** @var string */
+ protected $php_ext = null;
+
+ /** @var string */
+ protected $users_table = null;
+
+ /**
+ * Users loaded from the DB
+ *
+ * @var array Array of user data that we've loaded from the DB
+ */
+ protected $users = array();
+
+ /**
+ * User loader constructor
+ *
+ * @param phpbb_db_driver $db A database connection
+ * @param string $phpbb_root_path Path to the phpbb includes directory.
+ * @param string $php_ext php file extension
+ * @param string $users_table The name of the database table (phpbb_users)
+ */
+ public function __construct(phpbb_db_driver $db, $phpbb_root_path, $php_ext, $users_table)
+ {
+ $this->db = $db;
+
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+
+ $this->users_table = $users_table;
+ }
+
+ /**
+ * Load user helper
+ *
+ * @param array $user_ids
+ */
+ public function load_users(array $user_ids)
+ {
+ $user_ids[] = ANONYMOUS;
+
+ // Load the users
+ $user_ids = array_unique($user_ids);
+
+ // Do not load users we already have in $this->users
+ $user_ids = array_diff($user_ids, array_keys($this->users));
+
+ if (sizeof($user_ids))
+ {
+ $sql = 'SELECT *
+ FROM ' . $this->users_table . '
+ WHERE ' . $this->db->sql_in_set('user_id', $user_ids);
+ $result = $this->db->sql_query($sql);
+
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $this->users[$row['user_id']] = $row;
+ }
+ $this->db->sql_freeresult($result);
+ }
+ }
+
+ /**
+ * Load a user by username
+ *
+ * Stores the full data in the user cache so they do not need to be loaded again
+ * Returns the user id so you may use get_user() from the returned value
+ *
+ * @param string $username Raw username to load (will be cleaned)
+ * @return int User ID for the username
+ */
+ public function load_user_by_username($username)
+ {
+ $sql = 'SELECT *
+ FROM ' . $this->users_table . "
+ WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($username)) . "'";
+ $result = $this->db->sql_query($sql);
+ $row = $this->db->sql_fetchrow($result);
+ $this->db->sql_freeresult($result);
+
+ if ($row)
+ {
+ $this->users[$row['user_id']] = $row;
+
+ return $row['user_id'];
+ }
+
+ return ANONYMOUS;
+ }
+
+ /**
+ * Get a user row from our users cache
+ *
+ * @param int $user_id User ID of the user you want to retreive
+ * @param bool $query Should we query the database if this user has not yet been loaded?
+ * Typically this should be left as false and you should make sure
+ * you load users ahead of time with load_users()
+ * @return array|bool Row from the database of the user or Anonymous if the user wasn't loaded/does not exist
+ * or bool False if the anonymous user was not loaded
+ */
+ public function get_user($user_id, $query = false)
+ {
+ if (isset($this->users[$user_id]))
+ {
+ return $this->users[$user_id];
+ }
+ // Query them if we must (if ANONYMOUS is sent as the user_id and we have not loaded Anonymous yet, we must load Anonymous as a last resort)
+ else if ($query || $user_id == ANONYMOUS)
+ {
+ $this->load_users(array($user_id));
+
+ return $this->get_user($user_id);
+ }
+
+ return $this->get_user(ANONYMOUS);
+ }
+
+ /**
+ * Get username
+ *
+ * @param int $user_id User ID of the user you want to retreive the username for
+ * @param string $mode The mode to load (same as get_username_string). One of the following:
+ * profile (for getting an url to the profile)
+ * username (for obtaining the username)
+ * colour (for obtaining the user colour)
+ * full (for obtaining a html string representing a coloured link to the users profile)
+ * no_profile (the same as full but forcing no profile link)
+ * @param string $guest_username Optional parameter to specify the guest username. It will be used in favor of the GUEST language variable then.
+ * @param string $custom_profile_url Optional parameter to specify a profile url. The user id get appended to this url as &amp;u={user_id}
+ * @param bool $query Should we query the database if this user has not yet been loaded?
+ * Typically this should be left as false and you should make sure
+ * you load users ahead of time with load_users()
+ * @return string
+ */
+ public function get_username($user_id, $mode, $guest_username = false, $custom_profile_url = false, $query = false)
+ {
+ if (!($user = $this->get_user($user_id, $query)))
+ {
+ return '';
+ }
+
+ return get_username_string($mode, $user['user_id'], $user['username'], $user['user_colour'], $guest_username, $custom_profile_url);
+ }
+
+ /**
+ * Get avatar
+ *
+ * @param int $user_id User ID of the user you want to retreive the avatar for
+ * @param bool $query Should we query the database if this user has not yet been loaded?
+ * Typically this should be left as false and you should make sure
+ * you load users ahead of time with load_users()
+ * @return string
+ */
+ public function get_avatar($user_id, $query = false)
+ {
+ if (!($user = $this->get_user($user_id, $query)))
+ {
+ return '';
+ }
+
+ if (!function_exists('get_user_avatar'))
+ {
+ include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext);
+ }
+
+ return get_user_avatar($user['user_avatar'], $user['user_avatar_type'], $user['user_avatar_width'], $user['user_avatar_height']);
+ }
+
+ /**
+ * Get rank
+ *
+ * @param int $user_id User ID of the user you want to retreive the rank for
+ * @param bool $query Should we query the database if this user has not yet been loaded?
+ * Typically this should be left as false and you should make sure
+ * you load users ahead of time with load_users()
+ * @return array Array with keys 'rank_title', 'rank_img', and 'rank_img_src'
+ */
+ public function get_rank($user_id, $query = false)
+ {
+ if (!($user = $this->get_user($user_id, $query)))
+ {
+ return '';
+ }
+
+ if (!function_exists('get_user_rank'))
+ {
+ include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext);
+ }
+
+ $rank = array(
+ 'rank_title',
+ 'rank_img',
+ 'rank_img_src',
+ );
+
+ get_user_rank($user['user_rank'], (($user['user_id'] == ANONYMOUS) ? false : $user['user_posts']), $rank['rank_title'], $rank['rank_img'], $rank['rank_img_src']);
+
+ return $rank;
+ }
+}