aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--build/build.xml48
-rw-r--r--phpBB/adm/index.php2
-rw-r--r--phpBB/adm/style/acp_forums.html1
-rw-r--r--phpBB/adm/style/acp_main.html4
-rw-r--r--phpBB/adm/style/acp_users_overview.html1
-rw-r--r--phpBB/adm/style/overall_footer.html2
-rw-r--r--phpBB/adm/style/overall_header.html1
-rw-r--r--phpBB/adm/style/simple_footer.html1
-rw-r--r--phpBB/adm/style/simple_header.html1
-rw-r--r--phpBB/adm/swatch.php2
-rw-r--r--phpBB/assets/javascript/core.js6
-rw-r--r--phpBB/common.php1
-rw-r--r--phpBB/config/cron_tasks.yml16
-rw-r--r--phpBB/config/services.yml1
-rw-r--r--phpBB/docs/coding-guidelines.html52
-rw-r--r--phpBB/docs/events.md132
-rw-r--r--phpBB/docs/sphinx.sample.conf90
-rw-r--r--phpBB/download/file.php1
-rw-r--r--phpBB/includes/acp/acp_forums.php6
-rw-r--r--phpBB/includes/acp/acp_main.php18
-rw-r--r--phpBB/includes/acp/acp_permissions.php27
-rw-r--r--phpBB/includes/acp/acp_styles.php4
-rw-r--r--phpBB/includes/acp/info/acp_extensions.php4
-rw-r--r--phpBB/includes/acp/info/acp_language.php2
-rw-r--r--phpBB/includes/auth/auth.php22
-rw-r--r--phpBB/includes/bbcode.php2
-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/config/db.php6
-rw-r--r--phpBB/includes/cron/task/core/prune_all_forums.php4
-rw-r--r--phpBB/includes/cron/task/core/prune_forum.php4
-rw-r--r--phpBB/includes/cron/task/core/tidy_search.php4
-rw-r--r--phpBB/includes/db/db_tools.php2
-rw-r--r--phpBB/includes/db/driver/driver.php (renamed from phpBB/includes/db/dbal.php)17
-rw-r--r--phpBB/includes/db/driver/firebird.php (renamed from phpBB/includes/db/firebird.php)6
-rw-r--r--phpBB/includes/db/driver/mssql.php (renamed from phpBB/includes/db/mssql.php)6
-rw-r--r--phpBB/includes/db/driver/mssql_odbc.php (renamed from phpBB/includes/db/mssql_odbc.php)6
-rw-r--r--phpBB/includes/db/driver/mssqlnative.php (renamed from phpBB/includes/db/mssqlnative.php)6
-rw-r--r--phpBB/includes/db/driver/mysql.php (renamed from phpBB/includes/db/mysql.php)6
-rw-r--r--phpBB/includes/db/driver/mysqli.php (renamed from phpBB/includes/db/mysqli.php)6
-rw-r--r--phpBB/includes/db/driver/oracle.php (renamed from phpBB/includes/db/oracle.php)6
-rw-r--r--phpBB/includes/db/driver/postgres.php (renamed from phpBB/includes/db/postgres.php)11
-rw-r--r--phpBB/includes/db/driver/sqlite.php (renamed from phpBB/includes/db/sqlite.php)6
-rw-r--r--phpBB/includes/di/extension/config.php12
-rw-r--r--phpBB/includes/di/service_collection.php2
-rw-r--r--phpBB/includes/extension/manager.php4
-rw-r--r--phpBB/includes/extension/metadata_manager.php4
-rw-r--r--phpBB/includes/functions.php46
-rw-r--r--phpBB/includes/functions_admin.php54
-rw-r--r--phpBB/includes/functions_database_helper.php206
-rw-r--r--phpBB/includes/functions_display.php28
-rw-r--r--phpBB/includes/functions_download.php8
-rw-r--r--phpBB/includes/functions_install.php59
-rw-r--r--phpBB/includes/functions_messenger.php4
-rw-r--r--phpBB/includes/functions_posting.php5
-rw-r--r--phpBB/includes/functions_upload.php15
-rw-r--r--phpBB/includes/functions_user.php16
-rw-r--r--phpBB/includes/lock/db.php6
-rw-r--r--phpBB/includes/mcp/mcp_forum.php15
-rw-r--r--phpBB/includes/mcp/mcp_main.php22
-rw-r--r--phpBB/includes/mcp/mcp_topic.php56
-rw-r--r--phpBB/includes/message_parser.php11
-rw-r--r--phpBB/includes/questionnaire/questionnaire.php2
-rw-r--r--phpBB/includes/search/fulltext_mysql.php4
-rw-r--r--phpBB/includes/search/fulltext_native.php4
-rw-r--r--phpBB/includes/search/fulltext_postgres.php4
-rw-r--r--phpBB/includes/search/fulltext_sphinx.php6
-rw-r--r--phpBB/includes/sphinxapi.php3424
-rw-r--r--phpBB/includes/style/style.php27
-rw-r--r--phpBB/includes/template/compile.php13
-rw-r--r--phpBB/includes/template/filter.php141
-rw-r--r--phpBB/includes/template/locator.php4
-rw-r--r--phpBB/includes/template/template.php37
-rw-r--r--phpBB/install/convertors/convert_phpbb20.php2
-rw-r--r--phpBB/install/database_update.php220
-rw-r--r--phpBB/install/index.php2
-rw-r--r--phpBB/install/install_convert.php34
-rw-r--r--phpBB/install/install_install.php15
-rw-r--r--phpBB/install/install_update.php7
-rw-r--r--phpBB/language/en/acp/common.php10
-rw-r--r--phpBB/language/en/common.php2
-rw-r--r--phpBB/posting.php5
-rw-r--r--phpBB/styles/prosilver/template/ajax.js88
-rw-r--r--phpBB/styles/prosilver/template/forumlist_body.html1
-rw-r--r--phpBB/styles/prosilver/template/index_body.html4
-rw-r--r--phpBB/styles/prosilver/template/overall_footer.html9
-rw-r--r--phpBB/styles/prosilver/template/overall_header.html11
-rw-r--r--phpBB/styles/prosilver/template/posting_editor.html1
-rw-r--r--phpBB/styles/prosilver/template/simple_footer.html1
-rw-r--r--phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html2
-rw-r--r--phpBB/styles/prosilver/template/viewforum_body.html6
-rw-r--r--phpBB/styles/prosilver/template/viewtopic_body.html2
-rw-r--r--phpBB/styles/prosilver/template/viewtopic_print.html2
-rw-r--r--phpBB/styles/subsilver2/template/breadcrumbs.html3
-rw-r--r--phpBB/styles/subsilver2/template/forumlist_body.html1
-rw-r--r--phpBB/styles/subsilver2/template/index_body.html2
-rw-r--r--phpBB/styles/subsilver2/template/overall_footer.html6
-rw-r--r--phpBB/styles/subsilver2/template/overall_header.html3
-rw-r--r--phpBB/styles/subsilver2/template/posting_body.html1
-rw-r--r--phpBB/styles/subsilver2/template/simple_footer.html1
-rw-r--r--phpBB/styles/subsilver2/template/viewtopic_body.html2
-rw-r--r--phpBB/viewforum.php14
-rw-r--r--phpBB/viewtopic.php2
-rw-r--r--tests/RUNNING_TESTS.txt26
-rw-r--r--tests/bbcode/parser_test.php260
-rw-r--r--tests/compress/compress_test.php25
-rw-r--r--tests/dbal/connect_test.php4
-rw-r--r--tests/di/create_container_test.php3
-rw-r--r--tests/functional/acp_permissions_test.php128
-rw-r--r--tests/functional/auth_test.php23
-rw-r--r--tests/functional/fileupload_form_test.php4
-rw-r--r--tests/functional/memberlist_test.php43
-rw-r--r--tests/functional/posting_test.php149
-rw-r--r--tests/functions/convert_30_dbms_to_31_test.php40
-rw-r--r--tests/functions_database_helper/fixtures/bookmarks_duplicates.xml47
-rw-r--r--tests/functions_database_helper/fixtures/topics_watch_duplicates.xml80
-rw-r--r--tests/functions_database_helper/update_rows_avoiding_duplicates_notify_status_test.php101
-rw-r--r--tests/functions_database_helper/update_rows_avoiding_duplicates_test.php71
-rw-r--r--tests/mock/cache.php6
-rw-r--r--tests/mock/filesystem_extension_manager.php32
-rw-r--r--tests/mock/fileupload.php29
-rw-r--r--tests/mock/null_cache.php47
-rw-r--r--tests/session/append_sid_test.php108
-rw-r--r--tests/session/testable_factory.php4
-rw-r--r--tests/template/datasets/event_inheritance/ext/kappa/styles/all/template/test.html1
-rw-r--r--tests/template/datasets/event_inheritance/ext/kappa/styles/silver/template/test.html1
-rw-r--r--tests/template/datasets/event_inheritance/ext/kappa/styles/silver_inherit/template/test.html1
-rw-r--r--tests/template/datasets/event_inheritance/ext/omega/styles/all/template/test.html1
-rw-r--r--tests/template/datasets/event_inheritance/ext/omega/styles/silver/template/test.html1
-rw-r--r--tests/template/datasets/event_inheritance/ext/omega/styles/silver/template/two.html1
-rw-r--r--tests/template/datasets/event_inheritance/ext/zeta/styles/all/template/test.html1
-rw-r--r--tests/template/datasets/event_inheritance/styles/silver/template/event_test.html1
-rw-r--r--tests/template/datasets/event_inheritance/styles/silver/template/event_two.html1
-rw-r--r--tests/template/datasets/event_inheritance/styles/silver_inherit/template/event_test.html1
-rw-r--r--tests/template/datasets/ext_trivial/ext/trivial/styles/all/template/universal.html1
-rw-r--r--tests/template/datasets/ext_trivial/ext/trivial/styles/silver/template/simple.html1
-rw-r--r--tests/template/datasets/ext_trivial/styles/silver/template/event_simple.html1
-rw-r--r--tests/template/datasets/ext_trivial/styles/silver/template/event_universal.html1
-rw-r--r--tests/template/includephp_test.php2
-rw-r--r--tests/template/template_compile_test.php2
-rw-r--r--tests/template/template_events_test.php118
-rw-r--r--tests/template/template_test_case.php2
-rw-r--r--tests/template/template_test_case_with_tree.php2
-rw-r--r--tests/template/templates/events.html4
-rw-r--r--tests/test_framework/phpbb_database_test_case.php6
-rw-r--r--tests/test_framework/phpbb_database_test_connection_manager.php65
-rw-r--r--tests/test_framework/phpbb_functional_test_case.php162
-rw-r--r--tests/test_framework/phpbb_test_case_helpers.php11
-rw-r--r--tests/upload/filespec_test.php3
-rw-r--r--travis/phpunit-mysql-travis.xml2
-rw-r--r--travis/phpunit-postgres-travis.xml2
154 files changed, 4527 insertions, 2360 deletions
diff --git a/.gitignore b/.gitignore
index 4093aeb56d..c757210654 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,7 @@
/phpunit.xml
/phpBB/cache/*.html
/phpBB/cache/*.php
-/phpBB/cache/queue.php.lock
+/phpBB/cache/*.lock
/phpBB/composer.phar
/phpBB/config.php
/phpBB/config_dev.php
diff --git a/build/build.xml b/build/build.xml
index 28dd36bea8..8ce61e9374 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -2,17 +2,16 @@
<project name="phpBB" description="The phpBB forum software" default="all" basedir="../">
<!-- a few settings for the build -->
- <property name="newversion" value="3.0.11" />
- <property name="prevversion" value="3.0.10" />
- <property name="olderversions" value="3.0.2, 3.0.3, 3.0.4, 3.0.5, 3.0.6, 3.0.7, 3.0.7-PL1, 3.0.8, 3.0.9, 3.0.11-RC1, 3.0.11-RC2" />
+ <property name="newversion" value="3.1.0-dev" />
+ <property name="prevversion" value="3.0.11" />
+ <property name="olderversions" value="3.0.2, 3.0.3, 3.0.4, 3.0.5, 3.0.6, 3.0.7, 3.0.7-PL1, 3.0.8, 3.0.9, 3.0.10" />
<!-- no configuration should be needed beyond this point -->
<property name="oldversions" value="${olderversions}, ${prevversion}" />
<property name="versions" value="${oldversions}, ${newversion}" />
<!-- These are the main targets which you will probably want to use -->
- <target name="package" depends="clean,prepare,composer,create-package" />
- <target name="all" depends="clean,prepare,composer,test,docs,create-package" />
+ <target name="all" depends="clean,prepare,composer,test,docs,package" />
<target name="build" depends="clean,prepare,composer,test,docs" />
<target name="prepare">
@@ -43,9 +42,15 @@
<delete dir="build/save" />
</target>
- <target name="composer" depends="clean,prepare">
- <exec dir="./phpBB/"
- command="php ../composer.phar install"
+ <!--
+ This target basically just runs composer in the phpBB tree to ensure
+ all dependencies are loaded. Additional development dependencies are
+ loaded because testing framework may depend on them.
+ -->
+ <target name="composer">
+ <exec dir="phpBB"
+ command="php ../composer.phar install --dev"
+ checkreturn="true"
passthru="true" />
</target>
@@ -122,7 +127,7 @@
</target>
- <target name="create-package" depends="prepare-new-version,old-version-diffs">
+ <target name="package" depends="clean,prepare,prepare-new-version,old-version-diffs">
<exec dir="build" command="php -f package.php '${versions}' > logs/package.log" escape="false" />
<exec dir="build" command="php -f build_diff.php '${prevversion}' '${newversion}' > logs/build_diff.log" escape="false" />
@@ -162,9 +167,27 @@
command="git archive ${revision} | tar -xf - -C ../${dir}"
checkreturn="true" />
- <exec dir="${dir}"
- command="php ../composer.phar install"
- passthru="true" />
+ <!--
+ If composer.phar exists in this version of the tree, also export
+ it into ${dir}, install dependencies, then delete it again.
+ -->
+ <exec dir="."
+ command="git ls-tree ${revision} composer.phar"
+ checkreturn="true"
+ outputProperty='composer-ls-tree-output' />
+ <if>
+ <not><equals arg1="${composer-ls-tree-output}" arg2="" trim="true" /></not>
+ <then>
+ <exec dir="."
+ command="git archive ${revision} composer.phar | tar -xf - -C ${dir}"
+ checkreturn="true" />
+ <exec dir="${dir}"
+ command="php composer.phar install"
+ checkreturn="true"
+ passthru="true" />
+ <delete file="${dir}/composer.phar" />
+ </then>
+ </if>
<delete file="${dir}/config.php" />
<delete dir="${dir}/develop" />
@@ -187,6 +210,7 @@
<delete dir="${dir}/files" />
<delete dir="${dir}/install" />
<delete dir="${dir}/store" />
+ <delete dir="${dir}/vendor" />
</target>
</project>
diff --git a/phpBB/adm/index.php b/phpBB/adm/index.php
index 2591fa9d24..564a19ce64 100644
--- a/phpBB/adm/index.php
+++ b/phpBB/adm/index.php
@@ -51,7 +51,7 @@ $mode = request_var('mode', '');
// Set custom style for admin area
$phpbb_style->set_ext_dir_prefix('adm/');
-$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', '');
+$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), '');
$template->assign_var('T_ASSETS_PATH', $phpbb_root_path . 'assets');
$template->assign_var('T_TEMPLATE_PATH', $phpbb_admin_path . 'style');
diff --git a/phpBB/adm/style/acp_forums.html b/phpBB/adm/style/acp_forums.html
index 9a3706c2f0..d6c06bf6d8 100644
--- a/phpBB/adm/style/acp_forums.html
+++ b/phpBB/adm/style/acp_forums.html
@@ -241,6 +241,7 @@
<dt><label for="topics_per_page">{L_FORUM_TOPICS_PAGE}{L_COLON}</label><br /><span>{L_FORUM_TOPICS_PAGE_EXPLAIN}</span></dt>
<dd><input type="text" id="topics_per_page" name="topics_per_page" value="{TOPICS_PER_PAGE}" size="4" maxlength="4" /></dd>
</dl>
+ <!-- EVENT acp_forums_normal_settings_append -->
</fieldset>
<fieldset>
diff --git a/phpBB/adm/style/acp_main.html b/phpBB/adm/style/acp_main.html
index 8b591294d4..b644862ce1 100644
--- a/phpBB/adm/style/acp_main.html
+++ b/phpBB/adm/style/acp_main.html
@@ -76,6 +76,8 @@
</div>
<!-- ENDIF -->
+ <!-- EVENT acp_main_notice_after -->
+
<table cellspacing="1">
<caption>{L_FORUM_STATS}</caption>
<col class="col1" /><col class="col2" /><col class="col1" /><col class="col2" />
@@ -202,6 +204,8 @@
<dd><input type="hidden" name="action" value="purge_cache" /><input class="button2" type="submit" id="action_purge_cache" name="action_purge_cache" value="{L_RUN}" /></dd>
</dl>
</form>
+
+ <!-- EVENT acp_main_actions_append -->
</fieldset>
<!-- ENDIF -->
diff --git a/phpBB/adm/style/acp_users_overview.html b/phpBB/adm/style/acp_users_overview.html
index b7e3a9822e..a8e862c141 100644
--- a/phpBB/adm/style/acp_users_overview.html
+++ b/phpBB/adm/style/acp_users_overview.html
@@ -53,6 +53,7 @@
<dt><label for="password_confirm">{L_CONFIRM_PASSWORD}{L_COLON}</label><br /><span>{L_CONFIRM_PASSWORD_EXPLAIN}</span></dt>
<dd><input type="password" id="password_confirm" name="password_confirm" value="" autocomplete="off" /></dd>
</dl>
+<!-- EVENT acp_users_overview_options_append -->
<p class="quick">
<input class="button1" type="submit" name="update" value="{L_SUBMIT}" />
diff --git a/phpBB/adm/style/overall_footer.html b/phpBB/adm/style/overall_footer.html
index 2bc9ee52d7..2a8b46d458 100644
--- a/phpBB/adm/style/overall_footer.html
+++ b/phpBB/adm/style/overall_footer.html
@@ -42,5 +42,7 @@
<!-- INCLUDEJS ajax.js -->
{SCRIPTS}
+<!-- EVENT acp_overall_footer_after -->
+
</body>
</html>
diff --git a/phpBB/adm/style/overall_header.html b/phpBB/adm/style/overall_header.html
index f7902e6d2e..8170931221 100644
--- a/phpBB/adm/style/overall_header.html
+++ b/phpBB/adm/style/overall_header.html
@@ -155,6 +155,7 @@ function switch_menu()
// ]]>
</script>
+<!-- EVENT acp_overall_header_head_append -->
</head>
<body class="{S_CONTENT_DIRECTION}">
diff --git a/phpBB/adm/style/simple_footer.html b/phpBB/adm/style/simple_footer.html
index 1d5c62b0a3..906a9bebed 100644
--- a/phpBB/adm/style/simple_footer.html
+++ b/phpBB/adm/style/simple_footer.html
@@ -18,6 +18,7 @@
<script type="text/javascript" src="{T_JQUERY_LINK}"></script>
<!-- IF S_JQUERY_FALLBACK --><script type="text/javascript">window.jQuery || document.write(unescape('%3Cscript src="{T_ASSETS_PATH}/javascript/jquery.js?assets_version={T_ASSETS_VERSION}" type="text/javascript"%3E%3C/script%3E'));</script><!-- ENDIF -->
+<!-- EVENT acp_simple_footer_after -->
</body>
</html>
diff --git a/phpBB/adm/style/simple_header.html b/phpBB/adm/style/simple_header.html
index 0ba445ca2a..6e0c360600 100644
--- a/phpBB/adm/style/simple_header.html
+++ b/phpBB/adm/style/simple_header.html
@@ -101,6 +101,7 @@ function find_username(url)
// ]]>
</script>
+<!-- EVENT acp_simple_header_head_append -->
</head>
<body class="{S_CONTENT_DIRECTION}">
diff --git a/phpBB/adm/swatch.php b/phpBB/adm/swatch.php
index 5e8984db70..3ae38d0d8b 100644
--- a/phpBB/adm/swatch.php
+++ b/phpBB/adm/swatch.php
@@ -22,7 +22,7 @@ $auth->acl($user->data);
$user->setup();
// Set custom template for admin area
-$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', '');
+$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), '');
$template->set_filenames(array(
'body' => 'colour_swatch.html')
diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js
index f7d6688f68..c18bec7035 100644
--- a/phpBB/assets/javascript/core.js
+++ b/phpBB/assets/javascript/core.js
@@ -342,12 +342,6 @@ phpbb.ajaxify = function(options) {
var alert;
alert = phpbb.alert(dark.attr('data-ajax-error-title'), dark.attr('data-ajax-error-text'));
-
- setTimeout(function () {
- dark.fadeOut(phpbb.alert_time, function() {
- alert.hide();
- });
- }, 5000);
}
// If the element is a form, POST must be used and some extra data must
diff --git a/phpBB/common.php b/phpBB/common.php
index f974c9ba2f..3e0b8e324b 100644
--- a/phpBB/common.php
+++ b/phpBB/common.php
@@ -75,7 +75,6 @@ require($phpbb_root_path . 'includes/functions_content.' . $phpEx);
require($phpbb_root_path . 'includes/functions_container.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
-require($phpbb_root_path . 'includes/db/' . ltrim($dbms, 'dbal_') . '.' . $phpEx);
require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx);
// Set PHP error handler to ours
diff --git a/phpBB/config/cron_tasks.yml b/phpBB/config/cron_tasks.yml
index 74f57e449d..d1954b1877 100644
--- a/phpBB/config/cron_tasks.yml
+++ b/phpBB/config/cron_tasks.yml
@@ -6,6 +6,8 @@ services:
- %core.php_ext%
- @config
- @dbal.conn
+ calls:
+ - [set_name, [cron.task.core.prune_all_forums]]
tags:
- { name: cron.task }
@@ -16,6 +18,8 @@ services:
- %core.php_ext%
- @config
- @dbal.conn
+ calls:
+ - [set_name, [cron.task.core.prune_forum]]
tags:
- { name: cron.task }
@@ -25,6 +29,8 @@ services:
- %core.root_path%
- %core.php_ext%
- @config
+ calls:
+ - [set_name, [cron.task.core.queue]]
tags:
- { name: cron.task }
@@ -33,6 +39,8 @@ services:
arguments:
- @config
- @cache.driver
+ calls:
+ - [set_name, [cron.task.core.tidy_cache]]
tags:
- { name: cron.task }
@@ -42,6 +50,8 @@ services:
- %core.root_path%
- %core.php_ext%
- @config
+ calls:
+ - [set_name, [cron.task.core.tidy_database]]
tags:
- { name: cron.task }
@@ -54,6 +64,8 @@ services:
- @config
- @dbal.conn
- @user
+ calls:
+ - [set_name, [cron.task.core.tidy_search]]
tags:
- { name: cron.task }
@@ -62,6 +74,8 @@ services:
arguments:
- @config
- @user
+ calls:
+ - [set_name, [cron.task.core.tidy_sessions]]
tags:
- { name: cron.task }
@@ -71,5 +85,7 @@ services:
- %core.root_path%
- %core.php_ext%
- @config
+ calls:
+ - [set_name, [cron.task.core.tidy_warnings]]
tags:
- { name: cron.task }
diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml
index 242fe7f1d2..febdc5acb8 100644
--- a/phpBB/config/services.yml
+++ b/phpBB/config/services.yml
@@ -180,6 +180,7 @@ services:
- @user
- @style.resource_locator
- @template_context
+ - @ext.manager
template_context:
class: phpbb_template_context
diff --git a/phpBB/docs/coding-guidelines.html b/phpBB/docs/coding-guidelines.html
index eb569d12d5..14c2281323 100644
--- a/phpBB/docs/coding-guidelines.html
+++ b/phpBB/docs/coding-guidelines.html
@@ -72,6 +72,7 @@
<ol style="list-style-type: lower-roman;">
<li><a href="#templates">General Templating</a></li>
<li><a href="#stylestree">Styles Tree</a></li>
+ <li><a href="#template-events">Template Events</a></li>
</ol></li>
<li><a href="#charsets">Character Sets and Encodings</a></li>
<li><a href="#translation">Translation (<abbr title="Internationalisation">i18n</abbr>/<abbr title="Localisation">L10n</abbr>) Guidelines</a>
@@ -1678,6 +1679,57 @@ version = 3.1.0
parent = prosilver
</pre></div>
+ <a name="template-events"></a><h3>4.iii. Template Events</h3>
+ <p>Template events must follow this format: <code>&lt;!-- EVENT event_name --&gt;</code></p>
+ <p>Using the above example, files named <code>event_name.html</code> located within extensions will be injected into the location of the event.</p>
+
+ <h4>Template event naming guidelines:</h4>
+ <ul>
+ <li>An event name must be all lowercase, with each word separated by an underscore.</li>
+ <li>An event name must briefly describe the location and purpose of the event.</li>
+ <li>An event name must end with one of the following suffixes:</li>
+ <ul>
+ <li><code>_prepend</code> - This event adds an item to the beginning of a block of related items, or adds to the beginning of individual items in a block.</li>
+ <li><code>_append</code> - This event adds an item to the end of a block of related items, or adds to the end of individual items in a block.</li>
+ <li><code>_before</code> - This event adds content directly before the specified block</li>
+ <li><code>_after</code> - This event adds content directly after the specified block</li>
+ </ul>
+ </ul>
+
+ <h4>Template event documentation</h4>
+ <p>Events must be documented in <code>phpBB/docs/events.md</code> in alphabetical order based on the event name. The format is as follows:</p>
+
+ <ul><li>An event found in only one template file:
+ <div class="codebox"><pre>event_name
+===
+* Location: styles/&lt;style_name&gt;/template/filename.html
+* Purpose: A brief description of what this event should be used for.
+This may span multiple lines.
+</pre></div></li>
+ <li>An event found in multiple template files:
+ <div class="codebox"><pre>event_name
+===
+* Locations:
+ + first/file/path.html
+ + second/file/path.html
+* Purpose: Same as above.
+</pre></div>
+ <li>An event that is found multiple times in a file should have the number of instances in parenthesis next to the filename.
+ <div class="codebox"><pre>event_name
+===
+* Locations:
+ + first/file/path.html (2)
+ + second/file/path.html
+* Purpose: Same as above.
+</pre></div></li>
+ <li>An actual example event documentation:
+ <div class="codebox"><pre>forumlist_body_last_post_title_prepend
+====
+* Locations:
+ + styles/prosilver/template/forumlist_body.html
+ + styles/subsilver2/template/forumlist_body.html
+* Purpose: Add content before the post title of the latest post in a forum on the forum list.</pre></div></ul><br />
+
</div>
<div class="back2top"><a href="#wrap" class="top">Back to Top</a></div>
diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md
new file mode 100644
index 0000000000..f0b3b81822
--- /dev/null
+++ b/phpBB/docs/events.md
@@ -0,0 +1,132 @@
+acp_forums_normal_settings_append
+===
+* Location: adm/style/acp_forums.html
+* Purpose: Add settings to forums
+
+acp_main_actions_append
+===
+* Location: adm/style/acp_main.html
+* Purpose: Add actions to the ACP main page below the cache purge action
+
+acp_main_notice_after
+===
+* Location: adm/style/acp_main.html
+* Purpose: Add notices or other blocks in the ACP below other configuration notices
+
+acp_overall_footer_after
+===
+* Location: adm/style/overall_footer.html
+* Purpose: Add content below the footer in the ACP
+
+acp_overall_header_head_append
+===
+* Location: adm/style/overall_header.html
+* Add assets within the `<head>` tags in the ACP
+
+acp_simple_footer_after
+===
+* Location: adm/style/simple_footer.html
+* Purpose: Add content below the simple footer in the ACP
+
+acp_simple_header_head_append
+===
+* Location: adm/style/overall_header.html
+* Add assets within the `<head>` tags in the simple header of the ACP
+
+acp_users_overview_options_append
+===
+* Location: adm/style/acp_users.html
+* Purpose: Add options and settings on user overview page
+
+forumlist_body_last_post_title_prepend
+====
+* Locations:
+ + styles/prosilver/template/forumlist_body.html
+ + styles/subsilver2/template/forumlist_body.html
+* Purpose: Add content before the post title of the latest post in a forum on the forum list.
+
+index_body_stat_blocks_before
+===
+* Locations:
+ + styles/prosilver/template/index_body.html
+ + styles/subsilver2/template/index_body.html
+* Purpose: Add new statistic blocks above the Who Is Online and Board Statistics blocks
+
+overall_footer_after
+===
+* Locations:
+ + styles/prosilver/template/overall_footer.html
+ + styles/subsilver2/template/overall_footer.html
+* Purpose: Add content at the end of the file, directly prior to the `</body>` tag
+
+overall_footer_breadcrumb_append
+===
+* Location: styles/prosilver/template/overall_footer.html
+* Purpose: Add links to the list of breadcrumbs in the footer
+
+overall_footer_copyright_append
+===
+* Locations:
+ + styles/prosilver/template/overall_footer.html
+ + styles/subsilver2/template/overall_footer.html
+* Purpose: Add content after the copyright line (no new line by default), before the ACP link
+
+overall_footer_copyright_prepend
+===
+* Locations:
+ + styles/prosilver/template/overall_footer.html
+ + styles/subsilver2/template/overall_footer.html
+* Purpose: Add content before the copyright line
+
+overall_header_breadcrumb_append
+===
+* Locations:
+ + styles/prosilver/template/overall_header.html
+ + styles/subsilver2/template/breadcrumbs.html
+* Purpose: Add links to the list of breadcrumbs in the header
+
+overall_header_head_append
+===
+* Locations:
+ + styles/prosilver/template/overall_header.html
+ + styles/subsilver2/template/overall_header.html
+* Purpose: Add asset calls directly before the `</head>` tag
+
+overall_header_navigation_append
+===
+* Location: styles/prosilver/template/overall_header.html
+* Purpose: Add links after the navigation links in the header
+
+overall_header_navigation_prepend
+===
+* Location: styles/prosilver/template/overall_header.html
+* Purpose: Add links before the navigation links in the header
+
+posting_editor_options_prepend
+===
+* Locations:
+ + styles/prosilver/template/posting_editor.html
+ + styles/prosilver/template/posting_body.html
+* Purpose: Add posting options on the posting screen
+
+simple_footer_after
+===
+* Location: styles/prosilver/template/simple_footer.html
+* Purpose: Add content directly prior to the `</body>` tag of the simple footer
+
+ucp_pm_viewmessage_print_head_append
+===
+* Location: styles/prosilver/template/ucp_pm_viewmessage_print.html
+* Purpose: Add asset calls directly before the `</head>` tag of the Print PM screen
+
+viewtopic_print_head_append
+===
+* Location: styles/prosilver/template/viewtopic_print.html
+* Purpose: Add asset calls directly before the `</head>` tag of the Print Topic screen
+
+viewtopic_topic_title_prepend
+===
+* Locations:
+ + styles/prosilver/template/viewtopic_body.html
+ + styles/subsilver2/template/viewtopic_body.html
+* Purpose: Add content directly before the topic title link on the View topic screen
diff --git a/phpBB/docs/sphinx.sample.conf b/phpBB/docs/sphinx.sample.conf
index aa0e8d905d..fcaba6dcf3 100644
--- a/phpBB/docs/sphinx.sample.conf
+++ b/phpBB/docs/sphinx.sample.conf
@@ -10,21 +10,36 @@ source source_phpbb_{SPHINX_ID}_main
sql_query_pre = UPDATE phpbb_sphinx SET max_doc_id = MAX(post_id) WHERE counter_id = 1
sql_query_range = SELECT MIN(post_id), MAX(post_id) FROM phpbb_posts
sql_range_step = 5000
- sql_query = SELECT \
- p.post_id AS id, \
- p.forum_id, \
- p.topic_id, \
- p.poster_id, \
- CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, \
- p.post_time, \
- p.post_subject, \
- p.post_subject as title, \
- p.post_text as data, \
- t.topic_last_post_time, \
- 0 as deleted \
- FROM phpbb_posts p, phpbb_topics t \
- WHERE \
- p.topic_id = t.topic_id \
+ sql_query = SELECT
+\
+ p.post_id AS id,
+\
+ p.forum_id,
+\
+ p.topic_id,
+\
+ p.poster_id,
+\
+ CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post,
+\
+ p.post_time,
+\
+ p.post_subject,
+\
+ p.post_subject as title,
+\
+ p.post_text as data,
+\
+ t.topic_last_post_time,
+\
+ 0 as deleted
+\
+ FROM phpbb_posts p, phpbb_topics t
+\
+ WHERE
+\
+ p.topic_id = t.topic_id
+\
AND p.post_id >= $start AND p.post_id <= $end
sql_query_post =
sql_query_post_index = UPDATE phpbb_sphinx SET max_doc_id = $maxid WHERE counter_id = 1
@@ -42,21 +57,36 @@ source source_phpbb_{SPHINX_ID}_delta : source_phpbb_{SPHINX_ID}_main
{
sql_query_range =
sql_range_step =
- sql_query = SELECT \
- p.post_id AS id, \
- p.forum_id, \
- p.topic_id, \
- p.poster_id, \
- CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, \
- p.post_time, \
- p.post_subject, \
- p.post_subject as title, \
- p.post_text as data, \
- t.topic_last_post_time, \
- 0 as deleted \
- FROM phpbb_posts p, phpbb_topics t \
- WHERE \
- p.topic_id = t.topic_id \
+ sql_query = SELECT
+\
+ p.post_id AS id,
+\
+ p.forum_id,
+\
+ p.topic_id,
+\
+ p.poster_id,
+\
+ CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post,
+\
+ p.post_time,
+\
+ p.post_subject,
+\
+ p.post_subject as title,
+\
+ p.post_text as data,
+\
+ t.topic_last_post_time,
+\
+ 0 as deleted
+\
+ FROM phpbb_posts p, phpbb_topics t
+\
+ WHERE
+\
+ p.topic_id = t.topic_id
+\
AND p.post_id >= ( SELECT max_doc_id FROM phpbb_sphinx WHERE counter_id=1 )
sql_query_pre =
}
diff --git a/phpBB/download/file.php b/phpBB/download/file.php
index 73b9e0a9f2..8c282e36d7 100644
--- a/phpBB/download/file.php
+++ b/phpBB/download/file.php
@@ -43,7 +43,6 @@ if (isset($_GET['avatar']))
require($phpbb_root_path . 'includes/class_loader.' . $phpEx);
- require($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
require($phpbb_root_path . 'includes/functions.' . $phpEx);
require($phpbb_root_path . 'includes/functions_container.' . $phpEx);
diff --git a/phpBB/includes/acp/acp_forums.php b/phpBB/includes/acp/acp_forums.php
index c6dbf5eb9c..7e8d5d8388 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;
@@ -768,7 +768,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_main.php b/phpBB/includes/acp/acp_main.php
index d419bc3b99..c44bc1b8a6 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_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_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 f5953fb1dd..03d7059165 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',
+ 'title' => 'ACP_EXTENSIONS_MANAGEMENT',
'version' => '1.0.0',
'modes' => array(
- 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_GENERAL_TASKS')),
+ 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_EXTENSIONS_MANAGEMENT')),
),
);
}
diff --git a/phpBB/includes/acp/info/acp_language.php b/phpBB/includes/acp/info/acp_language.php
index 85dfb119ea..7f33a22fa6 100644
--- a/phpBB/includes/acp/info/acp_language.php
+++ b/phpBB/includes/acp/info/acp_language.php
@@ -19,7 +19,7 @@ class acp_language_info
'title' => 'ACP_LANGUAGE',
'version' => '1.0.0',
'modes' => array(
- 'lang_packs' => array('title' => 'ACP_LANGUAGE_PACKS', 'auth' => 'acl_a_language', 'cat' => array('ACP_GENERAL_TASKS')),
+ 'lang_packs' => array('title' => 'ACP_LANGUAGE_PACKS', 'auth' => 'acl_a_language', 'cat' => array('ACP_LANGUAGE')),
),
);
}
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/bbcode.php b/phpBB/includes/bbcode.php
index b9ffa8091c..e8681420d4 100644
--- a/phpBB/includes/bbcode.php
+++ b/phpBB/includes/bbcode.php
@@ -134,7 +134,7 @@ class bbcode
$style_resource_locator = new phpbb_style_resource_locator();
$style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider());
- $template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context());
+ $template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context(), $phpbb_extension_manager);
$style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, $style_path_provider, $template);
$style->set_style();
$template->set_filenames(array('bbcode.html' => 'bbcode.html'));
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/config/db.php b/phpBB/includes/config/db.php
index 45f9f1cb21..b18369a479 100644
--- a/phpBB/includes/config/db.php
+++ b/phpBB/includes/config/db.php
@@ -29,7 +29,7 @@ class phpbb_config_db extends phpbb_config
/**
* Database connection
- * @var dbal
+ * @var phpbb_db_driver
*/
protected $db;
@@ -42,11 +42,11 @@ class phpbb_config_db extends phpbb_config
/**
* Creates a configuration container with a default set of values
*
- * @param dbal $db Database connection
+ * @param phpbb_db_driver $db Database connection
* @param phpbb_cache_driver_interface $cache Cache instance
* @param string $table Configuration table name
*/
- public function __construct(dbal $db, phpbb_cache_driver_interface $cache, $table)
+ public function __construct(phpbb_db_driver $db, phpbb_cache_driver_interface $cache, $table)
{
$this->db = $db;
$this->cache = $cache;
diff --git a/phpBB/includes/cron/task/core/prune_all_forums.php b/phpBB/includes/cron/task/core/prune_all_forums.php
index ee0b5f7626..2c5d38cec0 100644
--- a/phpBB/includes/cron/task/core/prune_all_forums.php
+++ b/phpBB/includes/cron/task/core/prune_all_forums.php
@@ -37,9 +37,9 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base
* @param string $phpbb_root_path The root path
* @param string $php_ext The PHP extension
* @param phpbb_config $config The config
- * @param dbal $db The db connection
+ * @param phpbb_db_driver $db The db connection
*/
- public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, dbal $db)
+ public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, phpbb_db_driver $db)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
diff --git a/phpBB/includes/cron/task/core/prune_forum.php b/phpBB/includes/cron/task/core/prune_forum.php
index fa7a761d88..e3c497f072 100644
--- a/phpBB/includes/cron/task/core/prune_forum.php
+++ b/phpBB/includes/cron/task/core/prune_forum.php
@@ -47,9 +47,9 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p
* @param string $phpbb_root_path The root path
* @param string $php_ext The PHP extension
* @param phpbb_config $config The config
- * @param dbal $db The db connection
+ * @param phpbb_db_driver $db The db connection
*/
- public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, dbal $db)
+ public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, phpbb_db_driver $db)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
diff --git a/phpBB/includes/cron/task/core/tidy_search.php b/phpBB/includes/cron/task/core/tidy_search.php
index fdbe31346e..3ec25aa021 100644
--- a/phpBB/includes/cron/task/core/tidy_search.php
+++ b/phpBB/includes/cron/task/core/tidy_search.php
@@ -38,10 +38,10 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base
* @param string $php_ext The PHP extension
* @param phpbb_auth $auth The auth
* @param phpbb_config $config The config
- * @param dbal $db The db connection
+ * @param phpbb_db_driver $db The db connection
* @param phpbb_user $user The user
*/
- public function __construct($phpbb_root_path, $php_ext, phpbb_auth $auth, phpbb_config $config, dbal $db, phpbb_user $user)
+ public function __construct($phpbb_root_path, $php_ext, phpbb_auth $auth, phpbb_config $config, phpbb_db_driver $db, phpbb_user $user)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php
index 6df3aac9ce..2bb016cebd 100644
--- a/phpBB/includes/db/db_tools.php
+++ b/phpBB/includes/db/db_tools.php
@@ -300,7 +300,7 @@ class phpbb_db_tools
/**
* Constructor. Set DB Object and set {@link $return_statements return_statements}.
*
- * @param phpbb_dbal $db DBAL object
+ * @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)
diff --git a/phpBB/includes/db/dbal.php b/phpBB/includes/db/driver/driver.php
index c6a3638f9f..52ac21a79f 100644
--- a/phpBB/includes/db/dbal.php
+++ b/phpBB/includes/db/driver/driver.php
@@ -19,7 +19,7 @@ if (!defined('IN_PHPBB'))
* Database Abstraction Layer
* @package dbal
*/
-class dbal
+class phpbb_db_driver
{
var $db_connect_id;
var $query_result;
@@ -72,17 +72,17 @@ class dbal
/**
* Constructor
*/
- function dbal()
+ function __construct()
{
$this->num_queries = array(
- 'cached' => 0,
- 'normal' => 0,
- 'total' => 0,
+ 'cached' => 0,
+ 'normal' => 0,
+ 'total' => 0,
);
// Fill default sql layer based on the class being called.
// This can be changed by the specified layer itself later if needed.
- $this->sql_layer = substr(get_class($this), 5);
+ $this->sql_layer = substr(get_class($this), strlen('phpbb_db_driver_'));
// Do not change this please! This variable is used to easy the use of it - and is hardcoded.
$this->any_char = chr(0) . '%';
@@ -1042,8 +1042,3 @@ class dbal
return $rows_total;
}
}
-
-/**
-* This variable holds the class name to use later
-*/
-$sql_db = (!empty($dbms)) ? 'dbal_' . basename($dbms) : 'dbal';
diff --git a/phpBB/includes/db/firebird.php b/phpBB/includes/db/driver/firebird.php
index 5728eb901c..4767b29f63 100644
--- a/phpBB/includes/db/firebird.php
+++ b/phpBB/includes/db/driver/firebird.php
@@ -15,14 +15,12 @@ if (!defined('IN_PHPBB'))
exit;
}
-include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
-
/**
* Firebird/Interbase Database Abstraction Layer
* Minimum Requirement is Firebird 2.1
* @package dbal
*/
-class dbal_firebird extends dbal
+class phpbb_db_driver_firebird extends phpbb_db_driver
{
var $last_query_text = '';
var $service_handle = false;
@@ -272,7 +270,7 @@ class dbal_firebird extends dbal
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/mssql.php b/phpBB/includes/db/driver/mssql.php
index 235c48d3f0..215c6345c6 100644
--- a/phpBB/includes/db/mssql.php
+++ b/phpBB/includes/db/driver/mssql.php
@@ -15,14 +15,12 @@ if (!defined('IN_PHPBB'))
exit;
}
-include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
-
/**
* MSSQL Database Abstraction Layer
* Minimum Requirement is MSSQL 2000+
* @package dbal
*/
-class dbal_mssql extends dbal
+class phpbb_db_driver_mssql extends phpbb_db_driver
{
var $connect_error = '';
@@ -170,7 +168,7 @@ class dbal_mssql extends dbal
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/mssql_odbc.php b/phpBB/includes/db/driver/mssql_odbc.php
index 2194de4a5e..7d93f939a2 100644
--- a/phpBB/includes/db/mssql_odbc.php
+++ b/phpBB/includes/db/driver/mssql_odbc.php
@@ -15,8 +15,6 @@ if (!defined('IN_PHPBB'))
exit;
}
-include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
-
/**
* Unified ODBC functions
* Unified ODBC functions support any database having ODBC driver, for example Adabas D, IBM DB2, iODBC, Solid, Sybase SQL Anywhere...
@@ -28,7 +26,7 @@ include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
*
* @package dbal
*/
-class dbal_mssql_odbc extends dbal
+class phpbb_db_driver_mssql_odbc extends phpbb_db_driver
{
var $last_query_text = '';
var $connect_error = '';
@@ -199,7 +197,7 @@ class dbal_mssql_odbc extends dbal
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/mssqlnative.php b/phpBB/includes/db/driver/mssqlnative.php
index 4a969828e7..f88c702d07 100644
--- a/phpBB/includes/db/mssqlnative.php
+++ b/phpBB/includes/db/driver/mssqlnative.php
@@ -19,8 +19,6 @@ if (!defined('IN_PHPBB'))
exit;
}
-include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
-
/**
* Prior to version 1.1 the SQL Server Native PHP driver didn't support sqlsrv_num_rows, or cursor based seeking so we recall all rows into an array
* and maintain our own cursor index into that array.
@@ -193,7 +191,7 @@ class result_mssqlnative
/**
* @package dbal
*/
-class dbal_mssqlnative extends dbal
+class phpbb_db_driver_mssqlnative extends phpbb_db_driver
{
var $m_insert_id = NULL;
var $last_query_text = '';
@@ -339,7 +337,7 @@ class dbal_mssqlnative extends dbal
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/mysql.php b/phpBB/includes/db/driver/mysql.php
index dcdeec0401..30f78c9231 100644
--- a/phpBB/includes/db/mysql.php
+++ b/phpBB/includes/db/driver/mysql.php
@@ -15,8 +15,6 @@ if (!defined('IN_PHPBB'))
exit;
}
-include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
-
/**
* MySQL4 Database Abstraction Layer
* Compatible with:
@@ -26,7 +24,7 @@ include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
* MySQL 5.0+
* @package dbal
*/
-class dbal_mysql extends dbal
+class phpbb_db_driver_mysql extends phpbb_db_driver
{
var $multi_insert = true;
var $connect_error = '';
@@ -208,7 +206,7 @@ class dbal_mysql extends dbal
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/mysqli.php b/phpBB/includes/db/driver/mysqli.php
index 5713b1992c..4f45c5781c 100644
--- a/phpBB/includes/db/mysqli.php
+++ b/phpBB/includes/db/driver/mysqli.php
@@ -15,15 +15,13 @@ if (!defined('IN_PHPBB'))
exit;
}
-include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
-
/**
* MySQLi Database Abstraction Layer
* mysqli-extension has to be compiled with:
* MySQL 4.1+ or MySQL 5.0+
* @package dbal
*/
-class dbal_mysqli extends dbal
+class phpbb_db_driver_mysqli extends phpbb_db_driver
{
var $multi_insert = true;
var $connect_error = '';
@@ -203,7 +201,7 @@ class dbal_mysqli extends dbal
if ($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'))
diff --git a/phpBB/includes/db/oracle.php b/phpBB/includes/db/driver/oracle.php
index 4a2b107865..53f9dd71e0 100644
--- a/phpBB/includes/db/oracle.php
+++ b/phpBB/includes/db/driver/oracle.php
@@ -15,13 +15,11 @@ if (!defined('IN_PHPBB'))
exit;
}
-include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
-
/**
* Oracle Database Abstraction Layer
* @package dbal
*/
-class dbal_oracle extends dbal
+class phpbb_db_driver_oracle extends phpbb_db_driver
{
var $last_query_text = '';
var $connect_error = '';
@@ -448,7 +446,7 @@ class dbal_oracle extends dbal
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/postgres.php b/phpBB/includes/db/driver/postgres.php
index 8dfbfc3b60..cca97c9c0e 100644
--- a/phpBB/includes/db/postgres.php
+++ b/phpBB/includes/db/driver/postgres.php
@@ -15,19 +15,12 @@ if (!defined('IN_PHPBB'))
exit;
}
-include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
-
-if (!class_exists('phpbb_error_collector'))
-{
- include($phpbb_root_path . 'includes/error_collector.' . $phpEx);
-}
-
/**
* PostgreSQL Database Abstraction Layer
* Minimum Requirement is Version 7.3+
* @package dbal
*/
-class dbal_postgres extends dbal
+class phpbb_db_driver_postgres extends phpbb_db_driver
{
var $last_query_text = '';
var $connect_error = '';
@@ -218,7 +211,7 @@ class dbal_postgres extends dbal
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/sqlite.php b/phpBB/includes/db/driver/sqlite.php
index 06e368d586..155409b665 100644
--- a/phpBB/includes/db/sqlite.php
+++ b/phpBB/includes/db/driver/sqlite.php
@@ -15,14 +15,12 @@ if (!defined('IN_PHPBB'))
exit;
}
-include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
-
/**
* Sqlite Database Abstraction Layer
* Minimum Requirement: 2.8.2+
* @package dbal
*/
-class dbal_sqlite extends dbal
+class phpbb_db_driver_sqlite extends phpbb_db_driver
{
var $connect_error = '';
@@ -154,7 +152,7 @@ class dbal_sqlite extends dbal
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/di/extension/config.php b/phpBB/includes/di/extension/config.php
index 59b4c66870..6c272a6588 100644
--- a/phpBB/includes/di/extension/config.php
+++ b/phpBB/includes/di/extension/config.php
@@ -44,8 +44,8 @@ class phpbb_di_extension_config extends Extension
$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->fix_acm_type($acm_type));
- $container->setParameter('dbal.driver.class', 'dbal_'.$dbms);
+ $container->setParameter('cache.driver.class', $this->convert_30_acm_type($acm_type));
+ $container->setParameter('dbal.driver.class', phpbb_convert_30_dbms_to_31($dbms));
$container->setParameter('dbal.dbhost', $dbhost);
$container->setParameter('dbal.dbuser', $dbuser);
$container->setParameter('dbal.dbpasswd', $dbpasswd);
@@ -67,12 +67,12 @@ class phpbb_di_extension_config extends Extension
}
/**
- * Convert old (3.0) values to 3.1 class names
+ * Convert 3.0 ACM type to 3.1 cache driver class name
*
- * @param style $acm_type ACM type
- * @return ACM type class
+ * @param string $acm_type ACM type
+ * @return cache driver class
*/
- protected function fix_acm_type($acm_type)
+ protected function convert_30_acm_type($acm_type)
{
if (preg_match('#^[a-z]+$#', $acm_type))
{
diff --git a/phpBB/includes/di/service_collection.php b/phpBB/includes/di/service_collection.php
index 60323c8dba..880cb46d4d 100644
--- a/phpBB/includes/di/service_collection.php
+++ b/phpBB/includes/di/service_collection.php
@@ -43,7 +43,7 @@ class phpbb_di_service_collection extends ArrayObject
public function add($name)
{
$task = $this->container->get($name);
- $task->set_name($name);
+
$this->offsetSet($name, $task);
}
}
diff --git a/phpBB/includes/extension/manager.php b/phpBB/includes/extension/manager.php
index bfd4edde93..67cc81e407 100644
--- a/phpBB/includes/extension/manager.php
+++ b/phpBB/includes/extension/manager.php
@@ -34,7 +34,7 @@ class phpbb_extension_manager
/**
* Creates a manager and loads information from database
*
- * @param dbal $db A database connection
+ * @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
* @param string $phpbb_root_path Path to the phpbb includes directory.
@@ -42,7 +42,7 @@ 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(dbal $db, phpbb_config $config, $extension_table, $phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_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')
{
$this->phpbb_root_path = $phpbb_root_path;
$this->db = $db;
diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php
index ea85bd3c4e..36b0f8b184 100644
--- a/phpBB/includes/extension/metadata_manager.php
+++ b/phpBB/includes/extension/metadata_manager.php
@@ -34,12 +34,12 @@ class phpbb_extension_metadata_manager
/**
* Creates the metadata manager
*
- * @param dbal $db A database connection
+ * @param phpbb_db_driver $db A database connection
* @param string $extension_manager An instance of the phpbb extension manager
* @param string $phpbb_root_path Path to the phpbb includes directory.
* @param string $phpEx php file extension
*/
- public function __construct($ext_name, dbal $db, phpbb_extension_manager $extension_manager, $phpbb_root_path, $phpEx = '.php', phpbb_template $template, phpbb_config $config)
+ public function __construct($ext_name, phpbb_db_driver $db, phpbb_extension_manager $extension_manager, $phpbb_root_path, $phpEx = '.php', phpbb_template $template, phpbb_config $config)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->db = $db;
diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php
index 5fa5c5f505..d0ef2759d5 100644
--- a/phpBB/includes/functions.php
+++ b/phpBB/includes/functions.php
@@ -5550,6 +5550,52 @@ function phpbb_to_numeric($input)
}
/**
+* Convert either 3.0 dbms or 3.1 db driver class name to 3.1 db driver class name.
+*
+* If $dbms is a valid 3.1 db driver class name, returns it unchanged.
+* Otherwise prepends phpbb_db_driver_ to the dbms to convert a 3.0 dbms
+* to 3.1 db driver class name.
+*
+* @param string $dbms dbms parameter
+* @return db driver class
+*/
+function phpbb_convert_30_dbms_to_31($dbms)
+{
+ // Note: this check is done first because mysqli extension
+ // supplies a mysqli class, and class_exists($dbms) would return
+ // true for mysqli class.
+ // However, per the docblock any valid 3.1 driver name should be
+ // recognized by this function, and have priority over 3.0 dbms.
+ if (class_exists('phpbb_db_driver_' . $dbms))
+ {
+ return 'phpbb_db_driver_' . $dbms;
+ }
+
+ if (class_exists($dbms))
+ {
+ // Additionally we could check that $dbms extends phpbb_db_driver.
+ // http://php.net/manual/en/class.reflectionclass.php
+ // Beware of possible performance issues:
+ // http://stackoverflow.com/questions/294582/php-5-reflection-api-performance
+ // We could check for interface implementation in all paths or
+ // only when we do not prepend phpbb_db_driver_.
+
+ /*
+ $reflection = new \ReflectionClass($dbms);
+
+ if ($reflection->isSubclassOf('phpbb_db_driver'))
+ {
+ return $dbms;
+ }
+ */
+
+ return $dbms;
+ }
+
+ throw new \RuntimeException("You have specified an invalid dbms driver: $dbms");
+}
+
+/**
* Create a Symfony Request object from phpbb_request object
*
* @param phpbb_request $request Request object
diff --git a/phpBB/includes/functions_admin.php b/phpBB/includes/functions_admin.php
index 15930f9a2c..5529f2af46 100644
--- a/phpBB/includes/functions_admin.php
+++ b/phpBB/includes/functions_admin.php
@@ -2292,13 +2292,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);
@@ -2469,6 +2473,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.
*/
@@ -2740,12 +2758,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))
{
@@ -2855,6 +2877,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_database_helper.php b/phpBB/includes/functions_database_helper.php
new file mode 100644
index 0000000000..1b508e6a02
--- /dev/null
+++ b/phpBB/includes/functions_database_helper.php
@@ -0,0 +1,206 @@
+<?php
+/**
+*
+* @package phpBB3
+* @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;
+}
+
+/**
+* Updates rows in given table from a set of values to a new value.
+* If this results in rows violating uniqueness constraints, the duplicate
+* rows are eliminated.
+*
+* The only supported table is bookmarks.
+*
+* @param phpbb_db_driver $db Database object
+* @param string $table Table on which to perform the update
+* @param string $column Column whose values to change
+* @param array $from_values An array of values that should be changed
+* @param int $to_value The new value
+* @return null
+*/
+function phpbb_update_rows_avoiding_duplicates(phpbb_db_driver $db, $table, $column, $from_values, $to_value)
+{
+ $sql = "SELECT $column, user_id
+ FROM $table
+ WHERE " . $db->sql_in_set($column, $from_values);
+ $result = $db->sql_query($sql);
+
+ $old_user_ids = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $old_user_ids[$row[$column]][] = (int) $row['user_id'];
+ }
+ $db->sql_freeresult($result);
+
+ $sql = "SELECT $column, user_id
+ FROM $table
+ WHERE $column = " . (int) $to_value;
+ $result = $db->sql_query($sql);
+
+ $new_user_ids = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $new_user_ids[$row[$column]][] = (int) $row['user_id'];
+ }
+ $db->sql_freeresult($result);
+
+ $queries = array();
+ foreach ($from_values as $from_value)
+ {
+ if (!isset($old_user_ids[$from_value]))
+ {
+ continue;
+ }
+ if (empty($new_user_ids))
+ {
+ $sql = "UPDATE $table
+ SET $column = " . (int) $to_value . "
+ WHERE $column = '" . $db->sql_escape($from_value) . "'";
+ $queries[] = $sql;
+ }
+ else
+ {
+ $different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]);
+ if (!empty($different_user_ids))
+ {
+ $sql = "UPDATE $table
+ SET $column = " . (int) $to_value . "
+ WHERE $column = '" . $db->sql_escape($from_value) . "'
+ AND " . $db->sql_in_set('user_id', $different_user_ids);
+ $queries[] = $sql;
+ }
+ }
+ }
+
+ if (!empty($queries))
+ {
+ $db->sql_transaction('begin');
+
+ foreach ($queries as $sql)
+ {
+ $db->sql_query($sql);
+ }
+
+ $sql = "DELETE FROM $table
+ WHERE " . $db->sql_in_set($column, $from_values);
+ $db->sql_query($sql);
+
+ $db->sql_transaction('commit');
+ }
+}
+
+/**
+* Updates rows in given table from a set of values to a new value.
+* If this results in rows violating uniqueness constraints, the duplicate
+* rows are merged respecting notify_status (0 takes precedence over 1).
+*
+* The only supported table is topics_watch.
+*
+* @param phpbb_db_driver $db Database object
+* @param string $table Table on which to perform the update
+* @param string $column Column whose values to change
+* @param array $from_values An array of values that should be changed
+* @param int $to_value The new value
+* @return null
+*/
+function phpbb_update_rows_avoiding_duplicates_notify_status(phpbb_db_driver $db, $table, $column, $from_values, $to_value)
+{
+ $sql = "SELECT $column, user_id, notify_status
+ FROM $table
+ WHERE " . $db->sql_in_set($column, $from_values);
+ $result = $db->sql_query($sql);
+
+ $old_user_ids = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $old_user_ids[(int) $row['notify_status']][$row[$column]][] = (int) $row['user_id'];
+ }
+ $db->sql_freeresult($result);
+
+ $sql = "SELECT $column, user_id
+ FROM $table
+ WHERE $column = " . (int) $to_value;
+ $result = $db->sql_query($sql);
+
+ $new_user_ids = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $new_user_ids[$row[$column]][] = (int) $row['user_id'];
+ }
+ $db->sql_freeresult($result);
+
+ $queries = array();
+ $extra_updates = array(
+ 0 => 'notify_status = 0',
+ 1 => '',
+ );
+ foreach ($from_values as $from_value)
+ {
+ foreach ($extra_updates as $notify_status => $extra_update)
+ {
+ if (!isset($old_user_ids[$notify_status][$from_value]))
+ {
+ continue;
+ }
+ if (empty($new_user_ids))
+ {
+ $sql = "UPDATE $table
+ SET $column = " . (int) $to_value . "
+ WHERE $column = '" . $db->sql_escape($from_value) . "'";
+ $queries[] = $sql;
+ }
+ else
+ {
+ $different_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $new_user_ids[$to_value]);
+ if (!empty($different_user_ids))
+ {
+ $sql = "UPDATE $table
+ SET $column = " . (int) $to_value . "
+ WHERE $column = '" . $db->sql_escape($from_value) . "'
+ AND " . $db->sql_in_set('user_id', $different_user_ids);
+ $queries[] = $sql;
+ }
+
+ if ($extra_update)
+ {
+ $same_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $different_user_ids);
+ if (!empty($same_user_ids))
+ {
+ $sql = "UPDATE $table
+ SET $extra_update
+ WHERE $column = '" . (int) $to_value . "'
+ AND " . $db->sql_in_set('user_id', $same_user_ids);
+ $queries[] = $sql;
+ }
+ }
+ }
+ }
+ }
+
+ if (!empty($queries))
+ {
+ $db->sql_transaction('begin');
+
+ foreach ($queries as $sql)
+ {
+ $db->sql_query($sql);
+ }
+
+ $sql = "DELETE FROM $table
+ WHERE " . $db->sql_in_set($column, $from_values);
+ $db->sql_query($sql);
+
+ $db->sql_transaction('commit');
+ }
+}
diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php
index 73129803ee..cd4c901b58 100644
--- a/phpBB/includes/functions_display.php
+++ b/phpBB/includes/functions_display.php
@@ -61,6 +61,20 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod
{
markread('all', false, false, request_var('mark_time', 0));
+ if ($request->is_ajax())
+ {
+ // Tell the ajax script what language vars and URL need to be replaced
+ $data = array(
+ 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'],
+ 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'],
+ 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}index.$phpEx", 'hash=' . generate_link_hash('global') . '&mark=forums&mark_time=' . time()) : '',
+ 'MESSAGE_TITLE' => $user->lang['INFORMATION'],
+ 'MESSAGE_TEXT' => $user->lang['FORUMS_MARKED']
+ );
+ $json_response = new phpbb_json_response();
+ $json_response->send($data);
+ }
+
trigger_error(
$user->lang['FORUMS_MARKED'] . '<br /><br />' .
sprintf($user->lang['RETURN_INDEX'], '<a href="' . $redirect . '">', '</a>')
@@ -313,6 +327,20 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod
$message = sprintf($user->lang['RETURN_FORUM'], '<a href="' . $redirect . '">', '</a>');
meta_refresh(3, $redirect);
+ if ($request->is_ajax())
+ {
+ // Tell the ajax script what language vars and URL need to be replaced
+ $data = array(
+ 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'],
+ 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'],
+ 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . '&f=' . $root_data['forum_id'] . '&mark=forums&mark_time=' . time()) : '',
+ 'MESSAGE_TITLE' => $user->lang['INFORMATION'],
+ 'MESSAGE_TEXT' => $user->lang['FORUMS_MARKED']
+ );
+ $json_response = new phpbb_json_response();
+ $json_response->send($data);
+ }
+
trigger_error($user->lang['FORUMS_MARKED'] . '<br /><br />' . $message);
}
else
diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php
index fc6f1cc762..9ae647d806 100644
--- a/phpBB/includes/functions_download.php
+++ b/phpBB/includes/functions_download.php
@@ -596,7 +596,7 @@ function phpbb_parse_range_request($request_array, $filesize)
/**
* Increments the download count of all provided attachments
*
-* @param dbal $db The database object
+* @param phpbb_db_driver $db The database object
* @param array|int $ids The attach_id of each attachment
*
* @return null
@@ -617,7 +617,7 @@ function phpbb_increment_downloads($db, $ids)
/**
* Handles authentication when downloading attachments from a post or topic
*
-* @param dbal $db The database object
+* @param phpbb_db_driver $db The database object
* @param phpbb_auth $auth The authentication object
* @param int $topic_id The id of the topic that we are downloading from
*
@@ -651,7 +651,7 @@ function phpbb_download_handle_forum_auth($db, $auth, $topic_id)
/**
* Handles authentication when downloading attachments from PMs
*
-* @param dbal $db The database object
+* @param phpbb_db_driver $db The database object
* @param phpbb_auth $auth The authentication object
* @param int $user_id The user id
* @param int $msg_id The id of the PM that we are downloading from
@@ -678,7 +678,7 @@ function phpbb_download_handle_pm_auth($db, $auth, $user_id, $msg_id)
/**
* Checks whether a user can download from a particular PM
*
-* @param dbal $db The database object
+* @param phpbb_db_driver $db The database object
* @param int $user_id The user id
* @param int $msg_id The id of the PM that we are downloading from
*
diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php
index 1218ac401b..edb2c35fee 100644
--- a/phpBB/includes/functions_install.php
+++ b/phpBB/includes/functions_install.php
@@ -28,7 +28,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'SCHEMA' => 'firebird',
'MODULE' => 'interbase',
'DELIM' => ';;',
- 'DRIVER' => 'firebird',
+ 'DRIVER' => 'phpbb_db_driver_firebird',
'AVAILABLE' => true,
'2.0.x' => false,
),
@@ -37,7 +37,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'SCHEMA' => 'mysql_41',
'MODULE' => 'mysqli',
'DELIM' => ';',
- 'DRIVER' => 'mysqli',
+ 'DRIVER' => 'phpbb_db_driver_mysqli',
'AVAILABLE' => true,
'2.0.x' => true,
),
@@ -46,7 +46,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'SCHEMA' => 'mysql',
'MODULE' => 'mysql',
'DELIM' => ';',
- 'DRIVER' => 'mysql',
+ 'DRIVER' => 'phpbb_db_driver_mysql',
'AVAILABLE' => true,
'2.0.x' => true,
),
@@ -55,7 +55,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'SCHEMA' => 'mssql',
'MODULE' => 'mssql',
'DELIM' => 'GO',
- 'DRIVER' => 'mssql',
+ 'DRIVER' => 'phpbb_db_driver_mssql',
'AVAILABLE' => true,
'2.0.x' => true,
),
@@ -64,7 +64,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'SCHEMA' => 'mssql',
'MODULE' => 'odbc',
'DELIM' => 'GO',
- 'DRIVER' => 'mssql_odbc',
+ 'DRIVER' => 'phpbb_db_driver_mssql_odbc',
'AVAILABLE' => true,
'2.0.x' => true,
),
@@ -73,7 +73,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'SCHEMA' => 'mssql',
'MODULE' => 'sqlsrv',
'DELIM' => 'GO',
- 'DRIVER' => 'mssqlnative',
+ 'DRIVER' => 'phpbb_db_driver_mssqlnative',
'AVAILABLE' => true,
'2.0.x' => false,
),
@@ -82,7 +82,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'SCHEMA' => 'oracle',
'MODULE' => 'oci8',
'DELIM' => '/',
- 'DRIVER' => 'oracle',
+ 'DRIVER' => 'phpbb_db_driver_oracle',
'AVAILABLE' => true,
'2.0.x' => false,
),
@@ -91,7 +91,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'SCHEMA' => 'postgres',
'MODULE' => 'pgsql',
'DELIM' => ';',
- 'DRIVER' => 'postgres',
+ 'DRIVER' => 'phpbb_db_driver_postgres',
'AVAILABLE' => true,
'2.0.x' => true,
),
@@ -100,7 +100,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20
'SCHEMA' => 'sqlite',
'MODULE' => 'sqlite',
'DELIM' => ';',
- 'DRIVER' => 'sqlite',
+ 'DRIVER' => 'phpbb_db_driver_sqlite',
'AVAILABLE' => true,
'2.0.x' => false,
),
@@ -205,26 +205,19 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix,
$dbms = $dbms_details['DRIVER'];
- if ($load_dbal)
- {
- // Include the DB layer
- include($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
- }
-
// Instantiate it and set return on error true
- $sql_db = 'dbal_' . $dbms;
- $db = new $sql_db();
+ $db = new $dbms();
$db->sql_return_on_error(true);
// Check that we actually have a database name before going any further.....
- if ($dbms_details['DRIVER'] != 'sqlite' && $dbms_details['DRIVER'] != 'oracle' && $dbname === '')
+ if ($dbms_details['DRIVER'] != 'phpbb_db_driver_sqlite' && $dbms_details['DRIVER'] != 'phpbb_db_driver_oracle' && $dbname === '')
{
$error[] = $lang['INST_ERR_DB_NO_NAME'];
return false;
}
// Make sure we don't have a daft user who thinks having the SQLite database in the forum directory is a good idea
- if ($dbms_details['DRIVER'] == 'sqlite' && stripos(phpbb_realpath($dbhost), phpbb_realpath('../')) === 0)
+ if ($dbms_details['DRIVER'] == 'phpbb_db_driver_sqlite' && stripos(phpbb_realpath($dbhost), phpbb_realpath('../')) === 0)
{
$error[] = $lang['INST_ERR_DB_FORUM_PATH'];
return false;
@@ -233,8 +226,8 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix,
// Check the prefix length to ensure that index names are not too long and does not contain invalid characters
switch ($dbms_details['DRIVER'])
{
- case 'mysql':
- case 'mysqli':
+ case 'phpbb_db_driver_mysql':
+ case 'phpbb_db_driver_mysqli':
if (strspn($table_prefix, '-./\\') !== 0)
{
$error[] = $lang['INST_ERR_PREFIX_INVALID'];
@@ -243,22 +236,22 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix,
// no break;
- case 'postgres':
+ case 'phpbb_db_driver_postgres':
$prefix_length = 36;
break;
- case 'mssql':
- case 'mssql_odbc':
- case 'mssqlnative':
+ case 'phpbb_db_driver_mssql':
+ case 'phpbb_db_driver_mssql_odbc':
+ case 'phpbb_db_driver_mssqlnative':
$prefix_length = 90;
break;
- case 'sqlite':
+ case 'phpbb_db_driver_sqlite':
$prefix_length = 200;
break;
- case 'firebird':
- case 'oracle':
+ case 'phpbb_db_driver_firebird':
+ case 'phpbb_db_driver_oracle':
$prefix_length = 6;
break;
}
@@ -296,21 +289,21 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix,
// Make sure that the user has selected a sensible DBAL for the DBMS actually installed
switch ($dbms_details['DRIVER'])
{
- case 'mysqli':
+ case 'phpbb_db_driver_mysqli':
if (version_compare(mysqli_get_server_info($db->db_connect_id), '4.1.3', '<'))
{
$error[] = $lang['INST_ERR_DB_NO_MYSQLI'];
}
break;
- case 'sqlite':
+ case 'phpbb_db_driver_sqlite':
if (version_compare(sqlite_libversion(), '2.8.2', '<'))
{
$error[] = $lang['INST_ERR_DB_NO_SQLITE'];
}
break;
- case 'firebird':
+ case 'phpbb_db_driver_firebird':
// check the version of FB, use some hackery if we can't get access to the server info
if ($db->service_handle !== false && function_exists('ibase_server_info'))
{
@@ -391,7 +384,7 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix,
}
break;
- case 'oracle':
+ case 'phpbb_db_driver_oracle':
if ($unicode_check)
{
$sql = "SELECT *
@@ -413,7 +406,7 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix,
}
break;
- case 'postgres':
+ case 'phpbb_db_driver_postgres':
if ($unicode_check)
{
$sql = "SHOW server_encoding;";
diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php
index a18aeddabd..d0a02567ad 100644
--- a/phpBB/includes/functions_messenger.php
+++ b/phpBB/includes/functions_messenger.php
@@ -210,7 +210,7 @@ class messenger
{
$style_resource_locator = new phpbb_style_resource_locator();
$style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider());
- $tpl = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context());
+ $tpl = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context(), $phpbb_extension_manager);
$style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, $style_path_provider, $tpl);
$this->tpl_msg[$template_lang . $template_file] = $tpl;
@@ -231,7 +231,7 @@ class messenger
}
}
- $style->set_custom_style($template_lang . '_email', array($template_path, $fallback_template_path), '');
+ $style->set_custom_style($template_lang . '_email', array($template_path, $fallback_template_path), array(), '');
$tpl->set_filenames(array(
'body' => $template_file . '.txt',
diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php
index 171cf988ce..8aea27a9ef 100644
--- a/phpBB/includes/functions_posting.php
+++ b/phpBB/includes/functions_posting.php
@@ -1695,8 +1695,9 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u
// The variable name should be $post_approved, because it indicates if the post is approved or not
$post_approval = 1;
- // Check the permissions for post approval. Moderators are not affected.
- if (!$auth->acl_get('f_noapprove', $data['forum_id']) && !$auth->acl_get('m_approve', $data['forum_id']))
+ // Check the permissions for post approval.
+ // Moderators must go through post approval like ordinary users.
+ if (!$auth->acl_get('f_noapprove', $data['forum_id']))
{
// Post not approved, but in queue
$post_approval = 0;
diff --git a/phpBB/includes/functions_upload.php b/phpBB/includes/functions_upload.php
index b467aa93d1..4f31a85e83 100644
--- a/phpBB/includes/functions_upload.php
+++ b/phpBB/includes/functions_upload.php
@@ -70,7 +70,7 @@ class filespec
$this->mimetype = 'application/octetstream';
}
- $this->extension = strtolower($this->get_extension($this->realname));
+ $this->extension = strtolower(self::get_extension($this->realname));
// Try to get real filesize from temporary folder (not always working) ;)
$this->filesize = (@filesize($this->filename)) ? @filesize($this->filename) : $this->filesize;
@@ -187,8 +187,11 @@ class filespec
/**
* Get file extension
+ *
+ * @param string Filename that needs to be checked
+ * @return string Extension of the supplied filename
*/
- function get_extension($filename)
+ static public function get_extension($filename)
{
if (strpos($filename, '.') === false)
{
@@ -369,7 +372,7 @@ class filespec
}
// Check image type
- $types = $this->upload->image_types();
+ $types = fileupload::image_types();
if (!isset($types[$this->image_info[2]]) || !in_array($this->extension, $types[$this->image_info[2]]))
{
@@ -1019,9 +1022,11 @@ class fileupload
}
/**
- * Return image type/extension mapping
+ * Get image type/extension mapping
+ *
+ * @return array Array containing the image types and their extensions
*/
- function image_types()
+ static public function image_types()
{
return array(
IMAGETYPE_GIF => array('gif'),
diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php
index 8f9c9198f4..a50d5175fe 100644
--- a/phpBB/includes/functions_user.php
+++ b/phpBB/includes/functions_user.php
@@ -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);
@@ -3678,7 +3678,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 +3720,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/lock/db.php b/phpBB/includes/lock/db.php
index 6e94dd5a85..ccdaed0b28 100644
--- a/phpBB/includes/lock/db.php
+++ b/phpBB/includes/lock/db.php
@@ -48,7 +48,7 @@ class phpbb_lock_db
/**
* A database connection
- * @var dbal
+ * @var phpbb_db_driver
*/
private $db;
@@ -59,9 +59,9 @@ class phpbb_lock_db
*
* @param string $config_name A config variable to be used for locking
* @param array $config The phpBB configuration
- * @param dbal $db A database connection
+ * @param phpbb_db_driver $db A database connection
*/
- public function __construct($config_name, phpbb_config $config, dbal $db)
+ public function __construct($config_name, phpbb_config $config, phpbb_db_driver $db)
{
$this->config_name = $config_name;
$this->config = $config;
diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php
index 4dd5e5856a..0fad9e2a22 100644
--- a/phpBB/includes/mcp/mcp_forum.php
+++ b/phpBB/includes/mcp/mcp_forum.php
@@ -430,13 +430,16 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id)
// Message and return links
$success_msg = 'POSTS_MERGED_SUCCESS';
- // If the topic no longer exist, we will update the topic watch table.
- // To not let it error out on users watching both topics, we just return on an error...
- $db->sql_return_on_error(true);
- $db->sql_query('UPDATE ' . TOPICS_WATCH_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids));
- $db->sql_return_on_error(false);
+ if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status'))
+ {
+ include($phpbb_root_path . 'includes/functions_database_helper.' . $phpEx);
+ }
+
+ // Update the topic watch table.
+ phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id);
- $db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids));
+ // Update the bookmarks table.
+ phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', $topic_ids, $to_topic_id);
// Link to the new topic
$return_link .= (($return_link) ? '<br /><br />' : '') . sprintf($user->lang['RETURN_NEW_TOPIC'], '<a href="' . append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $to_forum_id . '&amp;t=' . $to_topic_id) . '">', '</a>');
diff --git a/phpBB/includes/mcp/mcp_main.php b/phpBB/includes/mcp/mcp_main.php
index 95ca7c2e1b..c28b466bcf 100644
--- a/phpBB/includes/mcp/mcp_main.php
+++ b/phpBB/includes/mcp/mcp_main.php
@@ -1080,6 +1080,7 @@ function mcp_fork_topic($topic_ids)
}
}
+ // Copy topic subscriptions to new topic
$sql = 'SELECT user_id, notify_status
FROM ' . TOPICS_WATCH_TABLE . '
WHERE topic_id = ' . $topic_id;
@@ -1100,6 +1101,27 @@ function mcp_fork_topic($topic_ids)
{
$db->sql_multi_insert(TOPICS_WATCH_TABLE, $sql_ary);
}
+
+ // Copy bookmarks to new topic
+ $sql = 'SELECT user_id
+ FROM ' . BOOKMARKS_TABLE . '
+ WHERE topic_id = ' . $topic_id;
+ $result = $db->sql_query($sql);
+
+ $sql_ary = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $sql_ary[] = array(
+ 'topic_id' => (int) $new_topic_id,
+ 'user_id' => (int) $row['user_id'],
+ );
+ }
+ $db->sql_freeresult($result);
+
+ if (sizeof($sql_ary))
+ {
+ $db->sql_multi_insert(BOOKMARKS_TABLE, $sql_ary);
+ }
}
// Sync new topics, parent forums and board stats
diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php
index 63ff7bed72..e3dd5a6b57 100644
--- a/phpBB/includes/mcp/mcp_topic.php
+++ b/phpBB/includes/mcp/mcp_topic.php
@@ -521,6 +521,49 @@ function split_topic($action, $topic_id, $to_forum_id, $subject)
WHERE post_id = {$post_id_list[0]}";
$db->sql_query($sql);
+ // Copy topic subscriptions to new topic
+ $sql = 'SELECT user_id, notify_status
+ FROM ' . TOPICS_WATCH_TABLE . '
+ WHERE topic_id = ' . $topic_id;
+ $result = $db->sql_query($sql);
+
+ $sql_ary = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $sql_ary[] = array(
+ 'topic_id' => (int) $to_topic_id,
+ 'user_id' => (int) $row['user_id'],
+ 'notify_status' => (int) $row['notify_status'],
+ );
+ }
+ $db->sql_freeresult($result);
+
+ if (sizeof($sql_ary))
+ {
+ $db->sql_multi_insert(TOPICS_WATCH_TABLE, $sql_ary);
+ }
+
+ // Copy bookmarks to new topic
+ $sql = 'SELECT user_id
+ FROM ' . BOOKMARKS_TABLE . '
+ WHERE topic_id = ' . $topic_id;
+ $result = $db->sql_query($sql);
+
+ $sql_ary = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $sql_ary[] = array(
+ 'topic_id' => (int) $to_topic_id,
+ 'user_id' => (int) $row['user_id'],
+ );
+ }
+ $db->sql_freeresult($result);
+
+ if (sizeof($sql_ary))
+ {
+ $db->sql_multi_insert(BOOKMARKS_TABLE, $sql_ary);
+ }
+
$success_msg = 'TOPIC_SPLIT_SUCCESS';
// Update forum statistics
@@ -623,13 +666,16 @@ function merge_posts($topic_id, $to_topic_id)
}
else
{
+ if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status'))
+ {
+ include($phpbb_root_path . 'includes/functions_database_helper.' . $phpEx);
+ }
+
// If the topic no longer exist, we will update the topic watch table.
- // To not let it error out on users watching both topics, we just return on an error...
- $db->sql_return_on_error(true);
- $db->sql_query('UPDATE ' . TOPICS_WATCH_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE topic_id = ' . (int) $topic_id);
- $db->sql_return_on_error(false);
+ phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id);
- $db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE topic_id = ' . (int) $topic_id);
+ // If the topic no longer exist, we will update the bookmarks table.
+ phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', $topic_id, $to_topic_id);
}
// Link to the new topic
diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php
index 1cd2a46fa1..44960dd78d 100644
--- a/phpBB/includes/message_parser.php
+++ b/phpBB/includes/message_parser.php
@@ -702,17 +702,6 @@ class bbcode_firstpass extends bbcode
{
global $config, $user;
- /**
- * If you change this code, make sure the cases described within the following reports are still working:
- * #3572 - [quote="[test]test"]test [ test[/quote] - (correct: parsed)
- * #14667 - [quote]test[/quote] test ] and [ test [quote]test[/quote] (correct: parsed)
- * #14770 - [quote="["]test[/quote] (correct: parsed)
- * [quote="[i]test[/i]"]test[/quote] (correct: parsed)
- * [quote="[quote]test[/quote]"]test[/quote] (correct: parsed - Username displayed as [quote]test[/quote])
- * #20735 - [quote]test[/[/b]quote] test [/quote][/quote] test - (correct: quoted: "test[/[/b]quote] test" / non-quoted: "[/quote] test" - also failed if layout distorted)
- * #40565 - [quote="a"]a[/quote][quote="a]a[/quote] (correct: first quote tag parsed, second quote tag unparsed)
- */
-
$in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
if (!$in)
diff --git a/phpBB/includes/questionnaire/questionnaire.php b/phpBB/includes/questionnaire/questionnaire.php
index 2d29fe978e..6f2727a38c 100644
--- a/phpBB/includes/questionnaire/questionnaire.php
+++ b/phpBB/includes/questionnaire/questionnaire.php
@@ -260,6 +260,8 @@ class phpbb_questionnaire_phpbb_data_provider
include("{$phpbb_root_path}config.$phpEx");
unset($dbhost, $dbport, $dbname, $dbuser, $dbpasswd); // Just a precaution
+ $dbms = phpbb_convert_30_dbms_to_31($dbms);
+
// Only send certain config vars
$config_vars = array(
'active_sessions' => true,
diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php
index ff2e24aa05..324c214e91 100644
--- a/phpBB/includes/search/fulltext_mysql.php
+++ b/phpBB/includes/search/fulltext_mysql.php
@@ -41,8 +41,8 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base
protected $config;
/**
- * DBAL object
- * @var dbal
+ * Database connection
+ * @var phpbb_db_driver
*/
protected $db;
diff --git a/phpBB/includes/search/fulltext_native.php b/phpBB/includes/search/fulltext_native.php
index 4623326fc7..53df8348ae 100644
--- a/phpBB/includes/search/fulltext_native.php
+++ b/phpBB/includes/search/fulltext_native.php
@@ -85,8 +85,8 @@ class phpbb_search_fulltext_native extends phpbb_search_base
protected $config;
/**
- * DBAL object
- * @var dbal
+ * Database connection
+ * @var phpbb_db_driver
*/
protected $db;
diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php
index 2880610655..f22ee2ca16 100644
--- a/phpBB/includes/search/fulltext_postgres.php
+++ b/phpBB/includes/search/fulltext_postgres.php
@@ -66,8 +66,8 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base
protected $config;
/**
- * DBAL object
- * @var dbal
+ * Database connection
+ * @var phpbb_db_driver
*/
protected $db;
diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php
index dd5634b623..4bacf74f93 100644
--- a/phpBB/includes/search/fulltext_sphinx.php
+++ b/phpBB/includes/search/fulltext_sphinx.php
@@ -84,8 +84,8 @@ class phpbb_search_fulltext_sphinx
protected $config;
/**
- * DBAL object
- * @var dbal
+ * Database connection
+ * @var phpbb_db_driver
*/
protected $db;
@@ -875,7 +875,7 @@ class phpbb_search_fulltext_sphinx
</dl>
<dl>
<dt><label for="fulltext_sphinx_indexer_mem_limit">' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN'] . '</span></dt>
- <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>
+ <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>
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/style.php b/phpBB/includes/style/style.php
index effd496fb9..4703c3a219 100644
--- a/phpBB/includes/style/style.php
+++ b/phpBB/includes/style/style.php
@@ -91,16 +91,22 @@ class phpbb_style
{
$style_path = $this->user->style['style_path'];
$style_dirs = ($this->user->style['style_parent_id']) ? array_reverse(explode('/', $this->user->style['style_parent_tree'])) : array();
- $paths = array($this->get_style_path($style_path));
+
+ $names = array($style_path);
foreach ($style_dirs as $dir)
{
- $paths[] = $this->get_style_path($dir);
+ $names[] = $dir;
}
+ // Add 'all' path, used as last fallback path by events and extensions
+ //$names[] = 'all';
- // Add 'all' path, used as last fallback path by hooks and extensions
- $paths[] = $this->get_style_path('all');
+ $paths = array();
+ foreach ($names as $name)
+ {
+ $paths[] = $this->get_style_path($name);
+ }
- return $this->set_custom_style($style_path, $paths);
+ return $this->set_custom_style($style_path, $paths, $names);
}
/**
@@ -110,18 +116,27 @@ class phpbb_style
*
* @param string $name Name of style, used for cache prefix. Examples: "admin", "prosilver"
* @param array or string $paths Array of style paths, relative to current root directory
+ * @param array $names Array of names of templates in inheritance tree order, used by extensions. If empty, $name will be used.
* @param string $template_path Path to templates, relative to style directory. False if path should be set to default (templates/).
*/
- public function set_custom_style($name, $paths, $template_path = false)
+ public function set_custom_style($name, $paths, $names = array(), $template_path = false)
{
if (is_string($paths))
{
$paths = array($paths);
}
+ if (empty($names))
+ {
+ $names = array($name);
+ }
+ $this->names = $names;
+
$this->provider->set_styles($paths);
$this->locator->set_paths($this->provider);
+ $this->template->set_style_names($names);
+
if ($template_path !== false)
{
$this->locator->set_template_path($template_path);
diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php
index 22da21820e..fcdaf7abda 100644
--- a/phpBB/includes/template/compile.php
+++ b/phpBB/includes/template/compile.php
@@ -35,16 +35,23 @@ class phpbb_template_compile
/**
* Constructor.
*
- * @param bool @allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag)
+ * @param bool $allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag)
+ * @param array $style_names Name of style to which the template being compiled belongs and parents in style tree order
* @param phpbb_style_resource_locator $locator Resource locator
* @param string $phpbb_root_path Path to phpBB root directory
+ * @param phpbb_extension_manager $extension_manager Extension manager to use for finding template fragments in extensions; if null, template events will not be invoked
+ * @param phpbb_user $user Current user
*/
- public function __construct($allow_php, $locator, $phpbb_root_path)
+ public function __construct($allow_php, $style_names, $locator, $phpbb_root_path, $extension_manager = null, $user = null)
{
$this->filter_params = array(
'allow_php' => $allow_php,
+ 'style_names' => $style_names,
'locator' => $locator,
- 'phpbb_root_path' => $phpbb_root_path
+ 'phpbb_root_path' => $phpbb_root_path,
+ 'extension_manager' => $extension_manager,
+ 'user' => $user,
+ 'template_compile' => $this,
);
}
diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php
index 66d28242a3..f73ad28ba1 100644
--- a/phpBB/includes/template/filter.php
+++ b/phpBB/includes/template/filter.php
@@ -88,6 +88,37 @@ class phpbb_template_filter extends php_user_filter
private $phpbb_root_path;
/**
+ * Name of the style that the template being compiled and/or rendered
+ * belongs to, and its parents, in inheritance tree order.
+ *
+ * Used to invoke style-specific template events.
+ *
+ * @var array
+ */
+ private $style_names;
+
+ /**
+ * Extension manager.
+ *
+ * @var phpbb_extension_manager
+ */
+ private $extension_manager;
+
+ /**
+ * Current user
+ *
+ * @var phpbb_user
+ */
+ private $user;
+
+ /**
+ * Template compiler.
+ *
+ * @var phpbb_template_compile
+ */
+ private $template_compile;
+
+ /**
* Stream filter
*
* Is invoked for evey chunk of the stream, allowing us
@@ -138,8 +169,10 @@ class phpbb_template_filter extends php_user_filter
/**
* Initializer, called on creation.
*
- * Get the allow_php option, root directory and locator from params,
+ * Get the allow_php option, style_names, root directory and locator from params,
* which are passed to stream_filter_append.
+ *
+ * @return boolean Returns true
*/
public function onCreate()
{
@@ -148,6 +181,13 @@ class phpbb_template_filter extends php_user_filter
$this->allow_php = $this->params['allow_php'];
$this->locator = $this->params['locator'];
$this->phpbb_root_path = $this->params['phpbb_root_path'];
+ $this->style_names = $this->params['style_names'];
+ $this->extension_manager = $this->params['extension_manager'];
+ if (isset($this->params['user']))
+ {
+ $this->user = $this->params['user'];
+ }
+ $this->template_compile = $this->params['template_compile'];
return true;
}
@@ -229,7 +269,9 @@ class phpbb_template_filter extends php_user_filter
}
/**
- * Callback for replacing matched tokens with PHP code
+ * Callback for replacing matched tokens with compiled template code.
+ *
+ * Compiled template code is an HTML stream with embedded PHP.
*
* @param array $matches Regular expression matches
* @return string compiled template code
@@ -317,6 +359,10 @@ class phpbb_template_filter extends php_user_filter
return '<!-- ENDPHP -->';
break;
+ case 'EVENT':
+ return '<?php ' . $this->compile_tag_event($matches[2]) . '?>';
+ break;
+
default:
return $matches[0];
break;
@@ -836,6 +882,97 @@ class phpbb_template_filter extends php_user_filter
}
/**
+ * Compile EVENT tag.
+ *
+ * $tag_args should be a single string identifying the event.
+ * The event name can contain letters, numbers and underscores only.
+ * If an invalid event name is specified, an E_USER_ERROR will be
+ * triggered.
+ *
+ * Event tags are only functional when the template engine has
+ * an instance of the extension manager. Extension manager would
+ * be called upon to find all extensions listening for the specified
+ * event, and to obtain additional template fragments. All such
+ * template fragments will be compiled and included in the generated
+ * compiled template code for the current template being compiled.
+ *
+ * The above means that whenever an extension is enabled or disabled,
+ * template cache should be cleared in order to update the compiled
+ * template code for the active set of template event listeners.
+ *
+ * This also means that extensions cannot return different template
+ * fragments at different times. Once templates are compiled, changing
+ * such template fragments would have no effect.
+ *
+ * @param string $tag_args EVENT tag arguments, as a string - for EVENT this is the event name
+ * @return string compiled template code
+ */
+ private function compile_tag_event($tag_args)
+ {
+ if (!preg_match('/^\w+$/', $tag_args))
+ {
+ // The event location is improperly formatted,
+ if ($this->user)
+ {
+ trigger_error($this->user->lang('ERR_TEMPLATE_EVENT_LOCATION', $tag_args), E_USER_ERROR);
+ }
+ else
+ {
+ trigger_error(sprintf('The specified template event location <em>[%s]</em> is improperly formatted.', $tag_args), E_USER_ERROR);
+ }
+ }
+ $location = $tag_args;
+
+ if ($this->extension_manager)
+ {
+ $finder = $this->extension_manager->get_finder();
+
+ $files = $finder
+ ->extension_prefix($location)
+ ->extension_suffix('.html')
+ ->extension_directory("/styles/all/template")
+ ->get_files();
+
+ foreach ($this->style_names as $style_name)
+ {
+ $more_files = $finder
+ ->extension_prefix($location)
+ ->extension_suffix('.html')
+ ->extension_directory("/styles/" . $style_name . "/template")
+ ->get_files();
+ if (!empty($more_files))
+ {
+ $files = array_merge($files, $more_files);
+ break;
+ }
+ }
+
+ $all_compiled = '';
+ foreach ($files as $file)
+ {
+ $compiled = $this->template_compile->compile_file($file);
+
+ if ($compiled === false)
+ {
+ if ($this->user)
+ {
+ trigger_error($this->user->lang('ERR_TEMPLATE_COMPILATION', phpbb_filter_root_path($file)), E_USER_ERROR);
+ }
+ else
+ {
+ trigger_error(sprintf('The file could not be compiled: %s', phpbb_filter_root_path($file)), E_USER_ERROR);
+ }
+ }
+
+ $all_compiled .= $compiled;
+ }
+ // Need spaces inside php tags as php cannot grok
+ // < ?php? > sans the spaces
+ return ' ?' . '>' . $all_compiled . '<?php ';
+ }
+ }
+
+ /**
* parse expression
* This is from Smarty
*/
diff --git a/phpBB/includes/template/locator.php b/phpBB/includes/template/locator.php
index 42db91efb2..f6fd20bcc2 100644
--- a/phpBB/includes/template/locator.php
+++ b/phpBB/includes/template/locator.php
@@ -39,7 +39,7 @@ interface phpbb_template_locator
* Sets the template filenames for handles. $filename_array
* should be a hash of handle => filename pairs.
*
- * @param array $filname_array Should be a hash of handle => filename pairs.
+ * @param array $filename_array Should be a hash of handle => filename pairs.
*/
public function set_filenames(array $filename_array);
@@ -66,7 +66,7 @@ interface phpbb_template_locator
* returns actually exists, it is faster than get_source_file_for_handle.
*
* Use get_source_file_for_handle to obtain the actual path that is
- * guaranteed to exist (which might come from the parent style
+ * guaranteed to exist (which might come from the parent style
* directory if primary style has parent styles).
*
* This function will trigger an error if the handle was never
diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php
index 5396ddbfad..bbec768613 100644
--- a/phpBB/includes/template/template.php
+++ b/phpBB/includes/template/template.php
@@ -75,14 +75,32 @@ class phpbb_template
private $locator;
/**
+ * Extension manager.
+ *
+ * @var phpbb_extension_manager
+ */
+ private $extension_manager;
+
+ /**
+ * Name of the style that the template being compiled and/or rendered
+ * belongs to, and its parents, in inheritance tree order.
+ *
+ * Used to invoke style-specific template events.
+ *
+ * @var array
+ */
+ private $style_names;
+
+ /**
* Constructor.
*
* @param string $phpbb_root_path phpBB root path
* @param user $user current user
* @param phpbb_template_locator $locator template locator
* @param phpbb_template_context $context template context
+ * @param phpbb_extension_manager $extension_manager extension manager, if null then template events will not be invoked
*/
- public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context)
+ public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context, phpbb_extension_manager $extension_manager = null)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
@@ -90,12 +108,13 @@ class phpbb_template
$this->user = $user;
$this->locator = $locator;
$this->context = $context;
+ $this->extension_manager = $extension_manager;
}
/**
* Sets the template filenames for handles.
*
- * @param array $filname_array Should be a hash of handle => filename pairs.
+ * @param array $filename_array Should be a hash of handle => filename pairs.
*/
public function set_filenames(array $filename_array)
{
@@ -105,6 +124,18 @@ class phpbb_template
}
/**
+ * Sets the style names corresponding to style hierarchy being compiled
+ * and/or rendered.
+ *
+ * @param array $style_names List of style names in inheritance tree order
+ * @return null
+ */
+ public function set_style_names(array $style_names)
+ {
+ $this->style_names = $style_names;
+ }
+
+ /**
* Clears all variables and blocks assigned to this template.
*/
public function destroy()
@@ -282,7 +313,7 @@ class phpbb_template
return new phpbb_template_renderer_include($output_file, $this);
}
- $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->locator, $this->phpbb_root_path);
+ $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->style_names, $this->locator, $this->phpbb_root_path, $this->extension_manager, $this->user);
if ($compile->compile_file_to_file($source_file, $output_file) !== false)
{
diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php
index 68acea02b3..5f30625980 100644
--- a/phpBB/install/convertors/convert_phpbb20.php
+++ b/phpBB/install/convertors/convert_phpbb20.php
@@ -24,6 +24,8 @@ if (!defined('IN_PHPBB'))
include($phpbb_root_path . 'config.' . $phpEx);
unset($dbpasswd);
+$dbms = phpbb_convert_30_dbms_to_31($dbms);
+
/**
* $convertor_data provides some basic information about this convertor which is
* used on the initial list of convertors and to populate the default settings
diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php
index 7795f8d7f7..bbb40d3976 100644
--- a/phpBB/install/database_update.php
+++ b/phpBB/install/database_update.php
@@ -87,7 +87,6 @@ phpbb_require_updated('includes/functions_content.' . $phpEx, true);
require($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
-require($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx);
phpbb_require_updated('includes/db/db_tools.' . $phpEx);
@@ -822,6 +821,70 @@ function _add_modules($modules_to_install)
$_module->remove_cache_file();
}
+/**
+* Add a new permission, optionally copy permission setting from another
+*
+* @param auth_admin $auth_admin auth_admin object
+* @param phpbb_db_driver $db Database object
+* @param string $permission_name Name of the permission to add
+* @param bool $is_global True is global, false is local
+* @param string $copy_from Optional permission name from which to copy
+* @return bool true on success, false on failure
+*/
+function _add_permission(auth_admin $auth_admin, phpbb_db_driver $db, $permission_name, $is_global = true, $copy_from = '')
+{
+ // Only add a permission that don't already exist
+ if (!empty($auth_admin->acl_options['id'][$permission_name]))
+ {
+ return true;
+ }
+
+ $permission_scope = $is_global ? 'global' : 'local';
+
+ $result = $auth_admin->acl_add_option(array(
+ $permission_scope => array($permission_name),
+ ));
+
+ if (!$result)
+ {
+ return $result;
+ }
+
+ // 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'][$permission_name];
+
+ $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE);
+
+ foreach ($tables as $table)
+ {
+ $sql = 'SELECT *
+ FROM ' . $table . '
+ WHERE auth_option_id = ' . $old_id;
+ $result = _sql($sql, $errored, $error_ary);
+
+ $sql_ary = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $row['auth_option_id'] = $new_id;
+ $sql_ary[] = $row;
+ }
+ $db->sql_freeresult($result);
+
+ if (sizeof($sql_ary))
+ {
+ $db->sql_multi_insert($table, $sql_ary);
+ }
+ }
+
+ $auth_admin->acl_clear_prefetch();
+ }
+
+ return true;
+}
+
/****************************************************************************
* ADD YOUR DATABASE SCHEMA CHANGES HERE *
*****************************************************************************/
@@ -1151,7 +1214,7 @@ function database_update_info()
*****************************************************************************/
function change_database_data(&$no_updates, $version)
{
- global $db, $errored, $error_ary, $config, $phpbb_root_path, $phpEx, $db_tools;
+ global $db, $db_tools, $errored, $error_ary, $config, $table_prefix, $phpbb_root_path, $phpEx;
$update_helpers = new phpbb_update_helpers();
@@ -1468,8 +1531,6 @@ function change_database_data(&$no_updates, $version)
),
);
- global $db_tools;
-
$statements = $db_tools->perform_schema_changes($changes);
foreach ($statements as $sql)
@@ -2111,26 +2172,41 @@ function change_database_data(&$no_updates, $version)
}
$db->sql_freeresult($result);
- global $db_tools, $table_prefix;
-
- // 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
+ /*
+ * Due to a bug, vanilla phpbb could not create captcha tables
+ * in 3.0.8 on firebird. It was possible for board administrators
+ * to adjust the code to work. If code was manually adjusted by
+ * board administrators, index names would not be the same as
+ * what 3.0.9 and newer expect. This code fragment drops captcha
+ * tables, destroying all entered Q&A captcha configuration, such
+ * that when Q&A is configured next the respective tables will be
+ * created with correct index names.
+ *
+ * If you wish to preserve your Q&A captcha configuration, you can
+ * manually rename indexes to the currently expected name:
+ * phpbb_captcha_questions_lang_iso => phpbb_captcha_questions_lang
+ * phpbb_captcha_answers_question_id => phpbb_captcha_answers_qid
+ *
+ * Again, this needs to be done only if a board was manually modified
+ * to fix broken captcha code.
+ *
if ($db_tools->sql_layer == 'firebird')
{
- $tables = array(
- $table_prefix . 'captcha_questions',
- $table_prefix . 'captcha_answers',
- $table_prefix . 'qa_confirm',
+ $changes = array(
+ 'drop_tables' => array(
+ $table_prefix . 'captcha_questions',
+ $table_prefix . 'captcha_answers',
+ $table_prefix . 'qa_confirm',
+ ),
);
- foreach ($tables as $table)
+ $statements = $db_tools->perform_schema_changes($changes);
+
+ foreach ($statements as $sql)
{
- if ($db_tools->sql_table_exists($table))
- {
- $db_tools->sql_table_drop($table);
- }
+ _sql($sql, $errored, $error_ary);
}
}
+ */
$no_updates = false;
break;
@@ -2463,6 +2539,12 @@ function change_database_data(&$no_updates, $version)
unset($next_legend);
}
+ // Rename styles module to Customise
+ $sql = 'UPDATE ' . MODULES_TABLE . "
+ SET module_langname = 'ACP_CAT_CUSTOMISE'
+ WHERE module_langname = 'ACP_CAT_STYLES'";
+ _sql($sql, $errored, $error_ary);
+
// Install modules
$modules_to_install = array(
'position' => array(
@@ -2500,10 +2582,67 @@ function change_database_data(&$no_updates, $version)
'auth' => '',
'cat' => 'UCP_PROFILE',
),
+ // To add a category, the mode and basename must be empty
+ // The mode is taken from the array key
+ '' => array(
+ 'base' => '',
+ 'class' => 'acp',
+ 'title' => 'ACP_EXTENSION_MANAGEMENT',
+ 'auth' => 'acl_a_extensions',
+ 'cat' => 'ACP_CAT_CUSTOMISE',
+ ),
+ 'extensions' => array(
+ 'base' => 'acp_extensions',
+ 'class' => 'acp',
+ 'title' => 'ACP_EXTENSIONS',
+ 'auth' => 'acl_a_extensions',
+ 'cat' => 'ACP_EXTENSION_MANAGEMENT',
+ ),
);
_add_modules($modules_to_install);
+ // We need a separate array for the new language sub heading
+ // because it requires another empty key
+ $modules_to_install = array(
+ '' => array(
+ 'base' => '',
+ 'class' => 'acp',
+ 'title' => 'ACP_LANGUAGE',
+ 'auth' => 'acl_a_language',
+ 'cat' => 'ACP_CAT_CUSTOMISE',
+ ),
+ );
+
+ _add_modules($modules_to_install);
+
+ // 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 = $db->sql_query($sql);
+ $language_module_id = $db->sql_fetchfield('module_id');
+ $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 = $db->sql_query($sql);
+ $language_management_module_id = $db->sql_fetchfield('module_id');
+ $db->sql_freeresult($result);
+
+ if (!class_exists('acp_modules'))
+ {
+ include($phpbb_root_path . 'includes/acp/acp_modules.' . $phpEx);
+ }
+ // acp_modules calls adm_back_link, which is undefined at this point
+ if (!function_exists('adm_back_link'))
+ {
+ include($phpbb_root_path . 'includes/functions_acp.' . $phpEx);
+ }
+ $module_manager = new acp_modules();
+ $module_manager->module_class = 'acp';
+ $module_manager->move_module($language_module_id, $language_management_module_id);
+
$sql = 'DELETE FROM ' . MODULES_TABLE . "
WHERE (module_basename = 'styles' OR module_basename = 'acp_styles') AND (module_mode = 'imageset' OR module_mode = 'theme' OR module_mode = 'template')";
_sql($sql, $errored, $error_ary);
@@ -2763,7 +2902,11 @@ function change_database_data(&$no_updates, $version)
set_config('board_timezone', $update_helpers->convert_phpbb30_timezone($config['board_timezone'], $config['board_dst']));
// After we have calculated the timezones we can delete user_dst column from user table.
- $db_tools->sql_column_remove(USERS_TABLE, 'user_dst');
+ $statements = $db_tools->sql_column_remove(USERS_TABLE, 'user_dst');
+ foreach ($statements as $sql)
+ {
+ _sql($sql, $errored, $error_ary);
+ }
}
if (!isset($config['site_home_url']))
@@ -2794,45 +2937,12 @@ function change_database_data(&$no_updates, $version)
}
$db->sql_freeresult($result);
- // Add new permission u_chgprofileinfo and duplicate settings from u_sig
+ // Add new permissions
include_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx);
$auth_admin = new auth_admin();
- // Only add the new permission if it does not already exist
- if (empty($auth_admin->acl_options['id']['u_chgprofileinfo']))
- {
- $auth_admin->acl_add_option(array('global' => array('u_chgprofileinfo')));
-
- // Now the tricky part, filling the permission
- $old_id = $auth_admin->acl_options['id']['u_sig'];
- $new_id = $auth_admin->acl_options['id']['u_chgprofileinfo'];
-
- $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE);
-
- foreach ($tables as $table)
- {
- $sql = 'SELECT *
- FROM ' . $table . '
- WHERE auth_option_id = ' . $old_id;
- $result = _sql($sql, $errored, $error_ary);
-
- $sql_ary = array();
- while ($row = $db->sql_fetchrow($result))
- {
- $row['auth_option_id'] = $new_id;
- $sql_ary[] = $row;
- }
- $db->sql_freeresult($result);
-
- if (sizeof($sql_ary))
- {
- $db->sql_multi_insert($table, $sql_ary);
- }
- }
-
- // Remove any old permission entries
- $auth_admin->acl_clear_prefetch();
- }
+ _add_permission($auth_admin, $db, 'u_chgprofileinfo', true, 'u_sig');
+ _add_permission($auth_admin, $db, 'a_extensions', true, 'a_styles');
// Update the auth setting for the module
$sql = 'UPDATE ' . MODULES_TABLE . "
diff --git a/phpBB/install/index.php b/phpBB/install/index.php
index 643695ee2b..8514f34e09 100644
--- a/phpBB/install/index.php
+++ b/phpBB/install/index.php
@@ -219,7 +219,7 @@ $phpbb_style_path_provider = new phpbb_style_path_provider();
$template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $phpbb_style_resource_locator, new phpbb_template_context());
$phpbb_style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $phpbb_style_resource_locator, $phpbb_style_path_provider, $template);
$phpbb_style->set_ext_dir_prefix('adm/');
-$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', '');
+$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), '');
$template->assign_var('T_ASSETS_PATH', '../assets');
$template->assign_var('T_TEMPLATE_PATH', $phpbb_admin_path . 'style');
diff --git a/phpBB/install/install_convert.php b/phpBB/install/install_convert.php
index 41de9de44c..15202768b8 100644
--- a/phpBB/install/install_convert.php
+++ b/phpBB/install/install_convert.php
@@ -121,10 +121,11 @@ class install_convert extends module
require($phpbb_root_path . 'config.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
- require($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
require($phpbb_root_path . 'includes/functions_convert.' . $phpEx);
- $db = new $sql_db();
+ $dbms = phpbb_convert_30_dbms_to_31($dbms);
+
+ $db = new $dbms();
$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, true);
unset($dbpasswd);
@@ -209,10 +210,11 @@ class install_convert extends module
require($phpbb_root_path . 'config.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
- require($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
require($phpbb_root_path . 'includes/functions_convert.' . $phpEx);
- $db = new $sql_db();
+ $dbms = phpbb_convert_30_dbms_to_31($dbms);
+
+ $db = new $dbms();
$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, true);
unset($dbpasswd);
@@ -332,10 +334,11 @@ class install_convert extends module
require($phpbb_root_path . 'config.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
- require($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
require($phpbb_root_path . 'includes/functions_convert.' . $phpEx);
- $db = new $sql_db();
+ $dbms = phpbb_convert_30_dbms_to_31($dbms);
+
+ $db = new $dbms();
$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, true);
unset($dbpasswd);
@@ -425,8 +428,7 @@ class install_convert extends module
if ($src_dbms != $dbms || $src_dbhost != $dbhost || $src_dbport != $dbport || $src_dbname != $dbname || $src_dbuser != $dbuser)
{
- $sql_db = 'dbal_' . $src_dbms;
- $src_db = new $sql_db();
+ $src_db = new $src_dbms();
$src_db->sql_connect($src_dbhost, $src_dbuser, htmlspecialchars_decode($src_dbpasswd), $src_dbname, $src_dbport, false, true);
$same_db = false;
}
@@ -575,10 +577,11 @@ class install_convert extends module
require($phpbb_root_path . 'config.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
- require($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
require($phpbb_root_path . 'includes/functions_convert.' . $phpEx);
- $db = new $sql_db();
+ $dbms = phpbb_convert_30_dbms_to_31($dbms);
+
+ $db = new $dbms();
$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, true);
unset($dbpasswd);
@@ -639,12 +642,8 @@ class install_convert extends module
$src_db = $same_db = null;
if ($convert->src_dbms != $dbms || $convert->src_dbhost != $dbhost || $convert->src_dbport != $dbport || $convert->src_dbname != $dbname || $convert->src_dbuser != $dbuser)
{
- if ($convert->src_dbms != $dbms)
- {
- require($phpbb_root_path . 'includes/db/' . $convert->src_dbms . '.' . $phpEx);
- }
- $sql_db = 'dbal_' . $convert->src_dbms;
- $src_db = new $sql_db();
+ $dbms = $convert->src_dbms;
+ $src_db = new $dbms();
$src_db->sql_connect($convert->src_dbhost, $convert->src_dbuser, htmlspecialchars_decode($convert->src_dbpasswd), $convert->src_dbname, $convert->src_dbport, false, true);
$same_db = false;
}
@@ -1539,6 +1538,7 @@ class install_convert extends module
function finish_conversion()
{
global $db, $phpbb_root_path, $phpEx, $convert, $config, $language, $user, $template;
+ global $cache, $auth;
$db->sql_query('DELETE FROM ' . CONFIG_TABLE . "
WHERE config_name = 'convert_progress'
@@ -1548,7 +1548,7 @@ class install_convert extends module
$db->sql_query('DELETE FROM ' . SESSIONS_TABLE);
@unlink($phpbb_root_path . 'cache/data_global.' . $phpEx);
- cache_moderators();
+ phpbb_cache_moderators($db, $cache, $auth);
// And finally, add a note to the log
add_log('admin', 'LOG_INSTALL_CONVERTED', $convert->convertor_data['forum_name'], $config['version']);
diff --git a/phpBB/install/install_install.php b/phpBB/install/install_install.php
index 197554d20f..dfd9cd6c22 100644
--- a/phpBB/install/install_install.php
+++ b/phpBB/install/install_install.php
@@ -1118,11 +1118,8 @@ class install_install extends module
$dbms = $available_dbms[$data['dbms']]['DRIVER'];
- // Load the appropriate database class if not already loaded
- include($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
-
// Instantiate the database
- $db = new $sql_db();
+ $db = new $dbms();
$db->sql_connect($data['dbhost'], $data['dbuser'], htmlspecialchars_decode($data['dbpasswd']), $data['dbname'], $data['dbport'], false, false);
// NOTE: trigger_error does not work here.
@@ -1418,11 +1415,8 @@ class install_install extends module
$dbms = $available_dbms[$data['dbms']]['DRIVER'];
- // Load the appropriate database class if not already loaded
- include($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
-
// Instantiate the database
- $db = new $sql_db();
+ $db = new $dbms();
$db->sql_connect($data['dbhost'], $data['dbuser'], htmlspecialchars_decode($data['dbpasswd']), $data['dbname'], $data['dbport'], false, false);
// NOTE: trigger_error does not work here.
@@ -2095,9 +2089,10 @@ class install_install extends module
'ACP_PERMISSION_ROLES',
'ACP_PERMISSION_MASKS',
),
- 'ACP_CAT_STYLES' => array(
+ 'ACP_CAT_CUSTOMISE' => array(
'ACP_STYLE_MANAGEMENT',
- 'ACP_STYLE_COMPONENTS',
+ 'ACP_EXTENSIONS_MANAGEMENT',
+ 'ACP_LANGUAGE',
),
'ACP_CAT_MAINTENANCE' => array(
'ACP_FORUM_LOGS',
diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php
index 55dba9a935..bfceed6b17 100644
--- a/phpBB/install/install_update.php
+++ b/phpBB/install/install_update.php
@@ -83,7 +83,6 @@ class install_update extends module
// Init DB
require($phpbb_root_path . 'config.' . $phpEx);
- require($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
// Special options for conflicts/modified files
@@ -92,7 +91,9 @@ class install_update extends module
define('MERGE_NEW_FILE', 3);
define('MERGE_MOD_FILE', 4);
- $db = new $sql_db();
+ $dbms = phpbb_convert_30_dbms_to_31($dbms);
+
+ $db = new $dbms();
// Connect to DB
$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, false);
@@ -131,7 +132,7 @@ class install_update extends module
}
// Set custom template again. ;)
- $phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', '');
+ $phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), '');
$template->assign_vars(array(
'S_USER_LANG' => $user->lang['USER_LANG'],
diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php
index 5eb10d50b3..427e4f4d98 100644
--- a/phpBB/language/en/acp/common.php
+++ b/phpBB/language/en/acp/common.php
@@ -59,6 +59,7 @@ $lang = array_merge($lang, array(
'ACP_CAPTCHA' => 'CAPTCHA',
+ 'ACP_CAT_CUSTOMISE' => 'Customise',
'ACP_CAT_DATABASE' => 'Database',
'ACP_CAT_DOT_MODS' => '.MODs',
'ACP_CAT_FORUMS' => 'Forums',
@@ -80,8 +81,10 @@ $lang = array_merge($lang, array(
'ACP_DISALLOW_USERNAMES' => 'Disallow usernames',
'ACP_EMAIL_SETTINGS' => 'Email settings',
- 'ACP_EXTENSION_GROUPS' => 'Manage extension groups',
- 'ACP_EXTENSIONS' => 'Manage board extensions',
+ 'ACP_EXTENSION_GROUPS' => 'Manage attachment extension groups',
+ 'ACP_EXTENSION_MANAGEMENT' => 'Extension management',
+ 'ACP_EXTENSIONS' => 'Extensions',
+
'ACP_FORUM_BASED_PERMISSIONS' => 'Forum based permissions',
'ACP_FORUM_LOGS' => 'Forum logs',
@@ -119,7 +122,7 @@ $lang = array_merge($lang, array(
'ACP_MANAGE_ATTACHMENTS' => 'Manage attachments',
'ACP_MANAGE_ATTACHMENTS_EXPLAIN' => 'Here you can list and delete files attached to posts and private messages.',
- 'ACP_MANAGE_EXTENSIONS' => 'Manage extensions',
+ 'ACP_MANAGE_EXTENSIONS' => 'Manage attachment extensions',
'ACP_MANAGE_FORUMS' => 'Manage forums',
'ACP_MANAGE_RANKS' => 'Manage ranks',
'ACP_MANAGE_REASONS' => 'Manage report/denial reasons',
@@ -166,7 +169,6 @@ $lang = array_merge($lang, array(
'ACP_SERVER_SETTINGS' => 'Server settings',
'ACP_SIGNATURE_SETTINGS' => 'Signature settings',
'ACP_SMILIES' => 'Smilies',
- 'ACP_STYLE_COMPONENTS' => 'Style components',
'ACP_STYLE_MANAGEMENT' => 'Style management',
'ACP_STYLES' => 'Styles',
'ACP_STYLES_CACHE' => 'Purge Cache',
diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php
index fbb7b40d41..6277457af7 100644
--- a/phpBB/language/en/common.php
+++ b/phpBB/language/en/common.php
@@ -186,6 +186,8 @@ $lang = array_merge($lang, array(
'ERR_CONNECTING_SERVER' => 'Error connecting to the server.',
'ERR_JAB_AUTH' => 'Could not authorise on Jabber server.',
'ERR_JAB_CONNECT' => 'Could not connect to Jabber server.',
+ 'ERR_TEMPLATE_EVENT_LOCATION' => 'The specified template event location <em>[%s]</em> is improperly formatted.',
+ 'ERR_TEMPLATE_COMPILATION' => 'The file could not be compiled: %s',
'ERR_UNABLE_TO_LOGIN' => 'The specified username or password is incorrect.',
'ERR_UNWATCHING' => 'An error occured while trying to unsubscribe.',
'ERR_WATCHING' => 'An error occured while trying to subscribe.',
diff --git a/phpBB/posting.php b/phpBB/posting.php
index 2d3cb9ab44..d32f7e33d9 100644
--- a/phpBB/posting.php
+++ b/phpBB/posting.php
@@ -1116,8 +1116,9 @@ if ($submit || $preview || $refresh)
$captcha->reset();
}
- // Check the permissions for post approval. Moderators are not affected.
- if ((!$auth->acl_get('f_noapprove', $data['forum_id']) && !$auth->acl_get('m_approve', $data['forum_id']) && empty($data['force_approved_state'])) || (isset($data['force_approved_state']) && !$data['force_approved_state']))
+ // Check the permissions for post approval.
+ // Moderators must go through post approval like ordinary users.
+ if ((!$auth->acl_get('f_noapprove', $data['forum_id']) && empty($data['force_approved_state'])) || (isset($data['force_approved_state']) && !$data['force_approved_state']))
{
meta_refresh(10, $redirect_url);
$message = ($mode == 'edit') ? $user->lang['POST_EDITED_MOD'] : $user->lang['POST_STORED_MOD'];
diff --git a/phpBB/styles/prosilver/template/ajax.js b/phpBB/styles/prosilver/template/ajax.js
index fa31d3268f..6637df4c3d 100644
--- a/phpBB/styles/prosilver/template/ajax.js
+++ b/phpBB/styles/prosilver/template/ajax.js
@@ -2,6 +2,94 @@
"use strict";
+/**
+* Close popup alert after a specified delay
+*
+* @param int Delay in ms until darkenwrapper's click event is triggered
+*/
+phpbb.closeDarkenWrapper = function(delay) {
+ setTimeout(function() {
+ $('#darkenwrapper').trigger('click');
+ }, delay);
+};
+
+// This callback will mark all forum icons read
+phpbb.add_ajax_callback('mark_forums_read', function(res) {
+ var readTitle = res.NO_UNREAD_POSTS;
+ var unreadTitle = res.UNREAD_POSTS;
+ var iconsArray = {
+ 'forum_unread': 'forum_read',
+ 'forum_unread_subforum': 'forum_read_subforum',
+ 'forum_unread_locked': 'forum_read_locked',
+ };
+
+ $('li.row').find('dl[class*="forum_unread"]').each(function() {
+ var $this = $(this);
+
+ $.each(iconsArray, function(unreadClass, readClass) {
+ if ($this.hasClass(unreadClass)) {
+ $this.removeClass(unreadClass).addClass(readClass);
+ }
+ });
+ $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle);
+ });
+
+ // Mark subforums read
+ $('a.subforum[class*="unread"]').removeClass('unread').addClass('read');
+
+ // Update mark forums read links
+ $('[data-ajax="mark_forums_read"]').attr('href', res.U_MARK_FORUMS);
+
+ phpbb.closeDarkenWrapper(3000);
+});
+
+// This callback will mark all topic icons read
+phpbb.add_ajax_callback('mark_topics_read', function(res) {
+ var readTitle = res.NO_UNREAD_POSTS;
+ var unreadTitle = res.UNREAD_POSTS;
+ var iconsArray = {
+ 'global_unread': 'global_read',
+ 'announce_unread': 'announce_read',
+ 'sticky_unread': 'sticky_read',
+ 'topic_unread': 'topic_read'
+ };
+ var iconsState = ['', '_hot', '_hot_mine', '_locked', '_locked_mine', '_mine'];
+ var unreadClassSelectors = '';
+ var classMap = {};
+ var classNames = [];
+
+ $.each(iconsArray, function(unreadClass, readClass) {
+ $.each(iconsState, function(key, value) {
+ // Only topics can be hot
+ if ((value == '_hot' || value == '_hot_mine') && unreadClass != 'topic_unread') {
+ return true;
+ }
+ classMap[unreadClass + value] = readClass + value;
+ classNames.push(unreadClass + value);
+ });
+ });
+
+ unreadClassSelectors = '.' + classNames.join(',.');
+
+ $('li.row').find(unreadClassSelectors).each(function() {
+ var $this = $(this);
+ $.each(classMap, function(unreadClass, readClass) {
+ if ($this.hasClass(unreadClass)) {
+ $this.removeClass(unreadClass).addClass(readClass);
+ }
+ });
+ $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle);
+ });
+
+ // Remove link to first unread post
+ $('a').has('span.icon_topic_newest').remove();
+
+ // Update mark topics read links
+ $('[data-ajax="mark_topics_read"]').attr('href', res.U_MARK_TOPICS);
+
+ phpbb.closeDarkenWrapper(3000);
+});
+
// This callback finds the post from the delete link, and removes it.
phpbb.add_ajax_callback('post_delete', function() {
var el = $(this),
diff --git a/phpBB/styles/prosilver/template/forumlist_body.html b/phpBB/styles/prosilver/template/forumlist_body.html
index 8762cb0c2e..539ed047c9 100644
--- a/phpBB/styles/prosilver/template/forumlist_body.html
+++ b/phpBB/styles/prosilver/template/forumlist_body.html
@@ -51,6 +51,7 @@
<!-- IF forumrow.U_UNAPPROVED_TOPICS --><a href="{forumrow.U_UNAPPROVED_TOPICS}">{UNAPPROVED_IMG}</a><!-- ENDIF -->
<!-- IF forumrow.LAST_POST_TIME --><dfn>{L_LAST_POST}</dfn>
<!-- IF forumrow.S_DISPLAY_SUBJECT -->
+ <!-- EVENT forumlist_body_last_post_title_prepend -->
<a href="{forumrow.U_LAST_POST}" title="{forumrow.LAST_POST_SUBJECT}" class="lastsubject">{forumrow.LAST_POST_SUBJECT_TRUNCATED}</a> <br />
<!-- ENDIF -->
{L_POST_BY_AUTHOR} {forumrow.LAST_POSTER_FULL}
diff --git a/phpBB/styles/prosilver/template/index_body.html b/phpBB/styles/prosilver/template/index_body.html
index 6babbf5997..57ad540a4a 100644
--- a/phpBB/styles/prosilver/template/index_body.html
+++ b/phpBB/styles/prosilver/template/index_body.html
@@ -8,7 +8,7 @@
<!-- IF S_DISPLAY_SEARCH -->
<li><a href="{U_SEARCH_UNANSWERED}">{L_SEARCH_UNANSWERED}</a><!-- IF S_LOAD_UNREADS --> &bull; <a href="{U_SEARCH_UNREAD}">{L_SEARCH_UNREAD}</a><!-- ENDIF --><!-- IF S_USER_LOGGED_IN --> &bull; <a href="{U_SEARCH_NEW}">{L_SEARCH_NEW}</a><!-- ENDIF --> &bull; <a href="{U_SEARCH_ACTIVE_TOPICS}">{L_SEARCH_ACTIVE_TOPICS}</a></li>
<!-- ENDIF -->
- <!-- IF not S_IS_BOT and U_MARK_FORUMS --><li class="rightside"><a href="{U_MARK_FORUMS}" accesskey="m" data-ajax="true">{L_MARK_FORUMS_READ}</a></li><!-- ENDIF -->
+ <!-- IF not S_IS_BOT and U_MARK_FORUMS --><li class="rightside"><a href="{U_MARK_FORUMS}" accesskey="m" data-ajax="mark_forums_read" data-overlay="false">{L_MARK_FORUMS_READ}</a></li><!-- ENDIF -->
</ul>
<!-- ENDIF -->
@@ -29,6 +29,8 @@
</form>
<!-- ENDIF -->
+<!-- EVENT index_body_stat_blocks_before -->
+
<!-- IF S_DISPLAY_ONLINE_LIST -->
<!-- IF U_VIEWONLINE --><h3><a href="{U_VIEWONLINE}">{L_WHO_IS_ONLINE}</a></h3><!-- ELSE --><h3>{L_WHO_IS_ONLINE}</h3><!-- ENDIF -->
<p>{TOTAL_USERS_ONLINE} ({L_ONLINE_EXPLAIN})<br />{RECORD_USERS}<br /> <br />{LOGGED_IN_USER_LIST}
diff --git a/phpBB/styles/prosilver/template/overall_footer.html b/phpBB/styles/prosilver/template/overall_footer.html
index 7e43ff2d0a..322d745501 100644
--- a/phpBB/styles/prosilver/template/overall_footer.html
+++ b/phpBB/styles/prosilver/template/overall_footer.html
@@ -6,7 +6,8 @@
<div class="inner">
<ul class="linklist">
- <li class="icon-home"><!-- IF U_SITE_HOME --><a href="{U_SITE_HOME}">{L_SITE_HOME}</a> <strong>&#8249;</strong> <!-- ENDIF --><a href="{U_INDEX}" accesskey="h">{L_INDEX}</a></li>
+ <li class="icon-home"><!-- IF U_SITE_HOME --><a href="{U_SITE_HOME}">{L_SITE_HOME}</a> <strong>&#8249;</strong> <!-- ENDIF --><a href="{U_INDEX}" accesskey="h">{L_INDEX}</a>
+ <!-- EVENT overall_footer_breadcrumb_append --></li>
<!-- IF not S_IS_BOT -->
<!-- IF U_WATCH_FORUM_LINK --><li <!-- IF S_WATCHING_FORUM -->class="icon-unsubscribe"<!-- ELSE -->class="icon-subscribe"<!-- ENDIF -->><a href="{U_WATCH_FORUM_LINK}" title="{S_WATCH_FORUM_TITLE}" data-ajax="toggle_link" data-toggle-class="icon-<!-- IF not S_WATCHING_FORUM -->unsubscribe<!-- ELSE -->subscribe<!-- ENDIF -->" data-toggle-text="{S_WATCH_FORUM_TOGGLE}" data-toggle-url="{U_WATCH_FORUM_TOGGLE}">{S_WATCH_FORUM_TITLE}</a></li><!-- ENDIF -->
<!-- IF U_WATCH_TOPIC --><li <!-- IF S_WATCHING_TOPIC -->class="icon-unsubscribe"<!-- ELSE -->class="icon-subscribe"<!-- ENDIF -->><a href="{U_WATCH_TOPIC}" title="{S_WATCH_TOPIC_TITLE}" data-ajax="toggle_link" data-toggle-class="<!-- IF not S_WATCHING_TOPIC -->icon-unsubscribe<!-- ELSE -->icon-subscribe<!-- ENDIF -->" data-toggle-text="{S_WATCH_TOPIC_TOGGLE}" data-toggle-url="{U_WATCH_TOPIC_TOGGLE}">{S_WATCH_TOPIC_TITLE}</a></li><!-- ENDIF -->
@@ -19,8 +20,11 @@
</div>
</div>
- <div class="copyright">{CREDIT_LINE}
+ <div class="copyright">
+ <!-- EVENT overall_footer_copyright_prepend -->
+ {CREDIT_LINE}
<!-- IF TRANSLATION_INFO --><br />{TRANSLATION_INFO}<!-- ENDIF -->
+ <!-- EVENT overall_footer_copyright_append -->
<!-- IF DEBUG_OUTPUT --><br />{DEBUG_OUTPUT}<!-- ENDIF -->
<!-- IF U_ACP --><br /><strong><a href="{U_ACP}">{L_ACP}</a></strong><!-- ENDIF -->
</div>
@@ -55,5 +59,6 @@
<!-- INCLUDEJS template/ajax.js -->
{SCRIPTS}
+<!-- EVENT overall_footer_after -->
</body>
</html>
diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html
index 3e00827235..97dbc76e43 100644
--- a/phpBB/styles/prosilver/template/overall_header.html
+++ b/phpBB/styles/prosilver/template/overall_header.html
@@ -84,8 +84,9 @@
<link href="{T_THEME_PATH}/tweaks.css?assets_version={T_ASSETS_VERSION}" rel="stylesheet" type="text/css" media="screen, projection" />
<![endif]-->
-</head>
+<!-- EVENT overall_header_head_append -->
+</head>
<body id="phpbb" class="section-{SCRIPT_NAME} {S_CONTENT_DIRECTION}">
<div id="wrap">
@@ -121,7 +122,11 @@
<ul class="linklist navlinks">
<!-- DEFINE $MICRODATA = ' itemtype="http://data-vocabulary.org/Breadcrumb" itemscope=""' -->
- <li class="icon-home"><!-- IF U_SITE_HOME --><a href="{U_SITE_HOME}"{$MICRODATA}>{L_SITE_HOME}</a> <strong>&#8249;</strong> <!-- ENDIF --><a href="{U_INDEX}" accesskey="h"{$MICRODATA}>{L_INDEX}</a> <!-- BEGIN navlinks --> <strong>&#8249;</strong> <a href="{navlinks.U_VIEW_FORUM}"{$MICRODATA}>{navlinks.FORUM_NAME}</a><!-- END navlinks --></li>
+ <li class="icon-home"><!-- IF U_SITE_HOME --><a href="{U_SITE_HOME}"{$MICRODATA}>{L_SITE_HOME}</a> <strong>&#8249;</strong> <!-- ENDIF -->
+ <a href="{U_INDEX}" accesskey="h"{$MICRODATA}>{L_INDEX}</a>
+ <!-- BEGIN navlinks --> <strong>&#8249;</strong> <a href="{navlinks.U_VIEW_FORUM}"{$MICRODATA}>{navlinks.FORUM_NAME}</a><!-- END navlinks -->
+ <!-- EVENT overall_header_breadcrumb_append -->
+ </li>
<!-- IF U_EMAIL_TOPIC --><li class="rightside"><a href="{U_EMAIL_TOPIC}" title="{L_EMAIL_TOPIC}" class="sendemail">{L_EMAIL_TOPIC}</a></li><!-- ENDIF -->
<!-- IF U_EMAIL_PM --><li class="rightside"><a href="{U_EMAIL_PM}" title="{L_EMAIL_PM}" class="sendemail">{L_EMAIL_PM}</a></li><!-- ENDIF -->
@@ -145,12 +150,14 @@
<!-- ENDIF -->
<ul class="linklist rightside">
+ <!-- EVENT overall_header_navigation_prepend -->
<li class="icon-faq"><a href="{U_FAQ}" title="{L_FAQ_EXPLAIN}">{L_FAQ}</a></li>
<!-- IF not S_IS_BOT -->
<!-- IF S_DISPLAY_MEMBERLIST --><li class="icon-members"><a href="{U_MEMBERLIST}" title="{L_MEMBERLIST_EXPLAIN}">{L_MEMBERLIST}</a></li><!-- ENDIF -->
<!-- IF not S_USER_LOGGED_IN and S_REGISTER_ENABLED and not (S_SHOW_COPPA or S_REGISTRATION) --><li class="icon-register"><a href="{U_REGISTER}">{L_REGISTER}</a></li><!-- ENDIF -->
<li class="icon-logout"><a href="{U_LOGIN_LOGOUT}" title="{L_LOGIN_LOGOUT}" accesskey="x">{L_LOGIN_LOGOUT}</a></li>
<!-- ENDIF -->
+ <!-- EVENT overall_header_navigation_append -->
</ul>
</div>
diff --git a/phpBB/styles/prosilver/template/posting_editor.html b/phpBB/styles/prosilver/template/posting_editor.html
index be45993cef..8156968098 100644
--- a/phpBB/styles/prosilver/template/posting_editor.html
+++ b/phpBB/styles/prosilver/template/posting_editor.html
@@ -213,6 +213,7 @@
<div class="inner">
<fieldset class="fields1">
+ <!-- EVENT posting_editor_options_prepend -->
<!-- IF S_BBCODE_ALLOWED -->
<div><label for="disable_bbcode"><input type="checkbox" name="disable_bbcode" id="disable_bbcode"{S_BBCODE_CHECKED} /> {L_DISABLE_BBCODE}</label></div>
<!-- ENDIF -->
diff --git a/phpBB/styles/prosilver/template/simple_footer.html b/phpBB/styles/prosilver/template/simple_footer.html
index 897e50ced7..ec189304fc 100644
--- a/phpBB/styles/prosilver/template/simple_footer.html
+++ b/phpBB/styles/prosilver/template/simple_footer.html
@@ -10,5 +10,6 @@
<!-- IF S_JQUERY_FALLBACK --><script type="text/javascript">window.jQuery || document.write(unescape('%3Cscript src="{T_ASSETS_PATH}/javascript/jquery.js?assets_version={T_ASSETS_VERSION}" type="text/javascript"%3E%3C/script%3E'));</script><!-- ENDIF -->
{SCRIPTS}
+<!-- EVENT simple_footer_after -->
</body>
</html>
diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html b/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html
index a024a170a7..4a8d3da08a 100644
--- a/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html
+++ b/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html
@@ -9,8 +9,8 @@
<title>{SITENAME} &bull; {PAGE_TITLE}</title>
<link href="{T_THEME_PATH}/print.css" rel="stylesheet" type="text/css" />
+<!-- EVENT ucp_pm_viewmessage_print_head_append -->
</head>
-
<body id="phpbb">
<div id="wrap">
<a id="top" accesskey="t"></a>
diff --git a/phpBB/styles/prosilver/template/viewforum_body.html b/phpBB/styles/prosilver/template/viewforum_body.html
index a3239602ae..b47c13d573 100644
--- a/phpBB/styles/prosilver/template/viewforum_body.html
+++ b/phpBB/styles/prosilver/template/viewforum_body.html
@@ -28,7 +28,7 @@
<!-- IF S_HAS_SUBFORUM -->
<!-- IF not S_IS_BOT and U_MARK_FORUMS -->
<ul class="linklist">
- <li class="rightside"><a href="{U_MARK_FORUMS}">{L_MARK_SUBFORUMS_READ}</a></li>
+ <li class="rightside"><a href="{U_MARK_FORUMS}" data-ajax="mark_forums_read" data-overlay="false">{L_MARK_SUBFORUMS_READ}</a></li>
</ul>
<!-- ENDIF -->
<!-- INCLUDE forumlist_body.html -->
@@ -57,7 +57,7 @@
<!-- IF .pagination or TOTAL_POSTS or TOTAL_TOPICS -->
<div class="pagination">
- <!-- IF not S_IS_BOT and U_MARK_TOPICS --><a href="{U_MARK_TOPICS}" accesskey="m" data-ajax="true">{L_MARK_TOPICS_READ}</a> &bull; <!-- ENDIF -->
+ <!-- IF not S_IS_BOT and U_MARK_TOPICS --><a href="{U_MARK_TOPICS}" accesskey="m" data-ajax="mark_topics_read" data-overlay="false">{L_MARK_TOPICS_READ}</a> &bull; <!-- ENDIF -->
<!-- IF TOTAL_TOPICS -->{TOTAL_TOPICS} &bull; <!-- ENDIF -->
<!-- IF .pagination -->
<!-- INCLUDE pagination.html -->
@@ -211,7 +211,7 @@
<!-- IF PAGE_NUMBER or TOTAL_POSTS or TOTAL_TOPICS -->
<div class="pagination">
- <!-- IF TOTAL_TOPICS and not S_IS_BOT and U_MARK_TOPICS --><a href="{U_MARK_TOPICS}">{L_MARK_TOPICS_READ}</a> &bull; <!-- ENDIF -->
+ <!-- IF TOTAL_TOPICS and not S_IS_BOT and U_MARK_TOPICS --><a href="{U_MARK_TOPICS}" data-ajax="mark_topics_read" data-overlay="false">{L_MARK_TOPICS_READ}</a> &bull; <!-- ENDIF -->
<!-- IF TOTAL_POSTS and not NEWEST_USER --> {TOTAL_POSTS}<!-- ELSEIF TOTAL_TOPICS and not NEWEST_USER --> {TOTAL_TOPICS} &bull; <!-- ENDIF -->
<!-- IF TOTAL_USERS -->{TOTAL_USERS} &bull; <!-- ENDIF -->
<!-- IF .pagination -->
diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html
index 1b7d78e063..7688898f5c 100644
--- a/phpBB/styles/prosilver/template/viewtopic_body.html
+++ b/phpBB/styles/prosilver/template/viewtopic_body.html
@@ -1,6 +1,6 @@
<!-- INCLUDE overall_header.html -->
<!-- IF U_MCP --><p>[&nbsp;<a href="{U_MCP}">{L_MCP}</a>&nbsp;]</p><!-- ENDIF -->
-<h2><a href="{U_VIEW_TOPIC}">{TOPIC_TITLE}</a></h2>
+<h2><!-- EVENT viewtopic_topic_title_prepend --><a href="{U_VIEW_TOPIC}">{TOPIC_TITLE}</a></h2>
<!-- NOTE: remove the style="display: none" when you want to have the forum description on the topic body -->
<!-- IF FORUM_DESC --><div style="display: none !important;">{FORUM_DESC}<br /></div><!-- ENDIF -->
diff --git a/phpBB/styles/prosilver/template/viewtopic_print.html b/phpBB/styles/prosilver/template/viewtopic_print.html
index 54676279d2..fd0b2dc3e6 100644
--- a/phpBB/styles/prosilver/template/viewtopic_print.html
+++ b/phpBB/styles/prosilver/template/viewtopic_print.html
@@ -9,8 +9,8 @@
<title>{SITENAME} &bull; {PAGE_TITLE}</title>
<link href="{T_THEME_PATH}/print.css" rel="stylesheet" type="text/css" />
+<!-- EVENT viewtopic_print_head_append -->
</head>
-
<body id="phpbb">
<div id="wrap">
<a id="top" accesskey="t"></a>
diff --git a/phpBB/styles/subsilver2/template/breadcrumbs.html b/phpBB/styles/subsilver2/template/breadcrumbs.html
index 49e31c0749..09ee9a8606 100644
--- a/phpBB/styles/subsilver2/template/breadcrumbs.html
+++ b/phpBB/styles/subsilver2/template/breadcrumbs.html
@@ -2,7 +2,8 @@
<table class="tablebg" width="100%" cellspacing="1" cellpadding="0" style="margin-top: 5px;">
<tr>
<td class="row1">
- <p class="breadcrumbs"><!-- IF U_SITE_HOME --><a href="{U_SITE_HOME}"{$MICRODATA}>{L_SITE_HOME}</a> <strong>&#187;</strong> <!-- ENDIF --><a href="{U_INDEX}"{$MICRODATA}>{L_INDEX}</a><!-- BEGIN navlinks --> &#187; <a href="{navlinks.U_VIEW_FORUM}"{$MICRODATA}>{navlinks.FORUM_NAME}</a><!-- END navlinks --></p>
+ <p class="breadcrumbs"><!-- IF U_SITE_HOME --><a href="{U_SITE_HOME}"{$MICRODATA}>{L_SITE_HOME}</a> <strong>&#187;</strong> <!-- ENDIF --><a href="{U_INDEX}"{$MICRODATA}>{L_INDEX}</a><!-- BEGIN navlinks --> &#187; <a href="{navlinks.U_VIEW_FORUM}"{$MICRODATA}>{navlinks.FORUM_NAME}</a><!-- END navlinks -->
+ <!-- EVENT overall_header_breadcrumb_append --></p>
<p class="datetime">{S_TIMEZONE}</p>
</td>
</tr>
diff --git a/phpBB/styles/subsilver2/template/forumlist_body.html b/phpBB/styles/subsilver2/template/forumlist_body.html
index ad505885f6..3e30561f3a 100644
--- a/phpBB/styles/subsilver2/template/forumlist_body.html
+++ b/phpBB/styles/subsilver2/template/forumlist_body.html
@@ -61,6 +61,7 @@
<td class="row2" align="center" nowrap="nowrap">
<!-- IF forumrow.LAST_POST_TIME -->
<!-- IF forumrow.S_DISPLAY_SUBJECT -->
+ <!-- EVENT forumlist_body_last_post_title_prepend -->
<p class="topicdetails"><a href="{forumrow.U_LAST_POST}" title="{forumrow.LAST_POST_SUBJECT}" class="lastsubject">{forumrow.LAST_POST_SUBJECT_TRUNCATED}</a></p>
<!-- ENDIF -->
<p class="topicdetails"><!-- IF forumrow.U_UNAPPROVED_TOPICS --><a href="{forumrow.U_UNAPPROVED_TOPICS}" class="imageset">{UNAPPROVED_IMG}</a>&nbsp;<!-- ENDIF -->{forumrow.LAST_POST_TIME}</p>
diff --git a/phpBB/styles/subsilver2/template/index_body.html b/phpBB/styles/subsilver2/template/index_body.html
index af2cefef7a..e07cfda8b3 100644
--- a/phpBB/styles/subsilver2/template/index_body.html
+++ b/phpBB/styles/subsilver2/template/index_body.html
@@ -18,6 +18,8 @@
<!-- INCLUDE breadcrumbs.html -->
+<!-- EVENT index_body_stat_blocks_before -->
+
<!-- IF S_DISPLAY_ONLINE_LIST -->
<br clear="all" />
diff --git a/phpBB/styles/subsilver2/template/overall_footer.html b/phpBB/styles/subsilver2/template/overall_footer.html
index 24c6f8105c..64e7587023 100644
--- a/phpBB/styles/subsilver2/template/overall_footer.html
+++ b/phpBB/styles/subsilver2/template/overall_footer.html
@@ -3,8 +3,11 @@
<div id="wrapfooter">
<!-- IF U_ACP --><span class="gensmall">[ <a href="{U_ACP}">{L_ACP}</a> ]</span><br /><br /><!-- ENDIF -->
- <span class="copyright">{CREDIT_LINE}
+ <span class="copyright">
+ <!-- EVENT overall_footer_copyright_prepend -->
+ {CREDIT_LINE}
<!-- IF TRANSLATION_INFO --><br />{TRANSLATION_INFO}<!-- ENDIF -->
+ <!-- EVENT overall_footer_copyright_append -->
<!-- IF DEBUG_OUTPUT --><br /><bdo dir="ltr">[ {DEBUG_OUTPUT} ]</bdo><!-- ENDIF --></span>
</div>
@@ -12,5 +15,6 @@
<!-- IF S_JQUERY_FALLBACK --><script type="text/javascript">window.jQuery || document.write(unescape('%3Cscript src="{T_ASSETS_PATH}/javascript/jquery.js?assets_version={T_ASSETS_VERSION}" type="text/javascript"%3E%3C/script%3E'));</script><!-- ENDIF -->
{SCRIPTS}
+<!-- EVENT overall_footer_after -->
</body>
</html>
diff --git a/phpBB/styles/subsilver2/template/overall_header.html b/phpBB/styles/subsilver2/template/overall_header.html
index 4ed69c56aa..d5fd20f0fe 100644
--- a/phpBB/styles/subsilver2/template/overall_header.html
+++ b/phpBB/styles/subsilver2/template/overall_header.html
@@ -129,6 +129,9 @@ function marklist(id, name, state)
// ]]>
</script>
+
+<!-- EVENT overall_header_head_append -->
+
</head>
<body class="{S_CONTENT_DIRECTION}">
diff --git a/phpBB/styles/subsilver2/template/posting_body.html b/phpBB/styles/subsilver2/template/posting_body.html
index 4c9df6f060..39f8f876ba 100644
--- a/phpBB/styles/subsilver2/template/posting_body.html
+++ b/phpBB/styles/subsilver2/template/posting_body.html
@@ -257,6 +257,7 @@
</td>
<td class="row2">
<table cellpadding="1">
+ <!-- EVENT posting_editor_options_prepend -->
<!-- IF S_BBCODE_ALLOWED -->
<tr>
<td><input type="checkbox" class="radio" name="disable_bbcode"{S_BBCODE_CHECKED} /></td>
diff --git a/phpBB/styles/subsilver2/template/simple_footer.html b/phpBB/styles/subsilver2/template/simple_footer.html
index 38a86c5001..c847bfedcc 100644
--- a/phpBB/styles/subsilver2/template/simple_footer.html
+++ b/phpBB/styles/subsilver2/template/simple_footer.html
@@ -9,5 +9,6 @@
<!-- IF S_JQUERY_FALLBACK --><script type="text/javascript">window.jQuery || document.write(unescape('%3Cscript src="{T_ASSETS_PATH}/javascript/jquery.js?assets_version={T_ASSETS_VERSION}" type="text/javascript"%3E%3C/script%3E'));</script><!-- ENDIF -->
{SCRIPTS}
+<!-- EVENT simple_footer_after -->
</body>
</html>
diff --git a/phpBB/styles/subsilver2/template/viewtopic_body.html b/phpBB/styles/subsilver2/template/viewtopic_body.html
index e2834ffa0b..9e6377022a 100644
--- a/phpBB/styles/subsilver2/template/viewtopic_body.html
+++ b/phpBB/styles/subsilver2/template/viewtopic_body.html
@@ -15,7 +15,7 @@
<!-- ENDIF -->
<div id="pageheader">
- <h2><a class="titles" href="{U_VIEW_TOPIC}">{TOPIC_TITLE}</a></h2>
+ <h2><!-- EVENT viewtopic_topic_title_prepend --><a class="titles" href="{U_VIEW_TOPIC}">{TOPIC_TITLE}</a></h2>
<!-- IF MODERATORS -->
<p class="moderators"><!-- IF S_SINGLE_MODERATOR -->{L_MODERATOR}<!-- ELSE -->{L_MODERATORS}<!-- ENDIF -->{L_COLON} {MODERATORS}</p>
diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php
index 83e5d4caa5..5fed514a12 100644
--- a/phpBB/viewforum.php
+++ b/phpBB/viewforum.php
@@ -181,6 +181,20 @@ if ($mark_read == 'topics')
$redirect_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id);
meta_refresh(3, $redirect_url);
+ if ($request->is_ajax())
+ {
+ // Tell the ajax script what language vars and URL need to be replaced
+ $data = array(
+ 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'],
+ 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'],
+ 'U_MARK_TOPICS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . "&f=$forum_id&mark=topics&mark_time=" . time()) : '',
+ 'MESSAGE_TITLE' => $user->lang['INFORMATION'],
+ 'MESSAGE_TEXT' => $user->lang['TOPICS_MARKED']
+ );
+ $json_response = new phpbb_json_response();
+ $json_response->send($data);
+ }
+
trigger_error($user->lang['TOPICS_MARKED'] . '<br /><br />' . sprintf($user->lang['RETURN_FORUM'], '<a href="' . $redirect_url . '">', '</a>'));
}
diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php
index 3fde5b5e03..bd2c7bea77 100644
--- a/phpBB/viewtopic.php
+++ b/phpBB/viewtopic.php
@@ -1353,7 +1353,7 @@ if (sizeof($attach_list))
}
$template->assign_vars(array(
- 'S_HAS_ATTACHMENTS' => !empty($attachments),
+ 'S_HAS_ATTACHMENTS' => $topic_data['topic_attachment'],
));
$methods = phpbb_gen_download_links('topic_id', $topic_id, $phpbb_root_path, $phpEx);
diff --git a/tests/RUNNING_TESTS.txt b/tests/RUNNING_TESTS.txt
index 75a6fc94f6..cede81d59d 100644
--- a/tests/RUNNING_TESTS.txt
+++ b/tests/RUNNING_TESTS.txt
@@ -17,7 +17,27 @@ PHP extensions
Unit tests use several PHP extensions that board code does not use. Currently
the following PHP extensions must be installed and enabled to run unit tests:
-- ctype
+- ctype (also a PHPUnit dependency)
+- dom (PHPUnit dependency)
+- json (also a phpBB dependency)
+
+Some of the functionality in phpBB and/or the test suite uses additional
+PHP extensions. If these extensions are not loaded, respective tests
+will be skipped:
+
+- apc (APC cache driver)
+- bz2 (compress tests)
+- interbase, pdo_firebird (Firebird database driver)
+- mysql, pdo_mysql (MySQL database driver)
+- mysqli, pdo_mysql (MySQLi database driver)
+- pcntl (flock class)
+- pdo (any database tests)
+- pgsql, pdo_pgsql (PostgreSQL database driver)
+- redis (https://github.com/nicolasff/phpredis, Redis cache driver)
+- simplexml (any database tests)
+- sqlite, pdo_sqlite (SQLite database driver, requires SQLite 2.x support
+ in pdo_sqlite)
+- zlib (compress tests)
Database Tests
--------------
@@ -30,7 +50,7 @@ example for mysqli can be found below. More information on configuration
options can be found on the wiki (see below).
<?php
- $dbms = 'mysqli';
+ $dbms = 'phpbb_db_driver_mysqli';
$dbhost = 'localhost';
$dbport = '';
$dbname = 'database';
@@ -44,7 +64,7 @@ to use in the environment as follows:
$ PHPBB_TEST_CONFIG=tests/test_config.php phpunit
Alternatively you can specify parameters in the environment, so e.g. the
-following will run phpunit with the same parameters as in the shown
+following will run PHPUnit with the same parameters as in the shown
test_config.php file:
$ PHPBB_TEST_DBMS='mysqli' PHPBB_TEST_DBHOST='localhost' \
diff --git a/tests/bbcode/parser_test.php b/tests/bbcode/parser_test.php
index 8c7fbc7128..d0dcce5bbf 100644
--- a/tests/bbcode/parser_test.php
+++ b/tests/bbcode/parser_test.php
@@ -2,28 +2,264 @@
/**
*
* @package testing
-* @version $Id$
-* @copyright (c) 2008 phpBB Group
+* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
-// require_once dirname(__FILE__) . '/../../phpBB/includes/bbcode/bbcode_parser_base.php';
-// require_once dirname(__FILE__) . '/../../phpBB/includes/bbcode/bbcode_parser.php';
+require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php';
+require_once dirname(__FILE__) . '/../../phpBB/includes/functions_content.php';
+require_once dirname(__FILE__) . '/../../phpBB/includes/bbcode.php';
+require_once dirname(__FILE__) . '/../../phpBB/includes/message_parser.php';
class phpbb_bbcode_parser_test extends PHPUnit_Framework_TestCase
{
- public function test_both_passes()
+ public function bbcode_firstpass_data()
{
- $this->markTestIncomplete('New bbcode parser has not been backported from feature/ascraeus-experiment yet.');
-
- $parser = new phpbb_bbcode_parser();
+ return array(
+ // Default bbcodes from in their simplest way
+ array(
+ 'Test default bbcodes: simple bold',
+ '[b]bold[/b]',
+ '[b:]bold[/b:]',
+ ),
+ array(
+ 'Test default bbcodes: simple underlined',
+ '[u]underlined[/u]',
+ '[u:]underlined[/u:]',
+ ),
+ array(
+ 'Test default bbcodes: simple italic',
+ '[i]italic[/i]',
+ '[i:]italic[/i:]',
+ ),
+ array(
+ 'Test default bbcodes: simple color rgb',
+ '[color=#FF0000]colored[/color]',
+ '[color=#FF0000:]colored[/color:]',
+ ),
+ array(
+ 'Test default bbcodes: simple color name',
+ '[color=red]colored[/color]',
+ '[color=red:]colored[/color:]',
+ ),
+ array(
+ 'Test default bbcodes: simple size',
+ '[size=75]smaller[/size]',
+ '[size=75:]smaller[/size:]',
+ ),
+ array(
+ 'Test default bbcodes: simple quote',
+ '[quote]quoted[/quote]',
+ '[quote:]quoted[/quote:]',
+ ),
+ array(
+ 'Test default bbcodes: simple quote with username',
+ '[quote=&quot;username&quot;]quoted[/quote]',
+ '[quote=&quot;username&quot;:]quoted[/quote:]',
+ ),
+ array(
+ 'Test default bbcodes: simple code',
+ '[code]unparsed code[/code]',
+ '[code:]unparsed code[/code:]',
+ ),
+ array(
+ 'Test default bbcodes: simple php code',
+ '[code=php]unparsed code[/code]',
+ '[code=php:]<span class="syntaxdefault">unparsed&nbsp;code</span>[/code:]',
+ ),
+ array(
+ 'Test default bbcodes: simple list',
+ '[list]no item[/list]',
+ '[list:]no item[/list:u:]',
+ ),
+ array(
+ 'Test default bbcodes: simple list-item only',
+ '[*]unparsed',
+ '[*]unparsed',
+ ),
+ array(
+ 'Test default bbcodes: simple list-item',
+ '[list][*]item[/list]',
+ '[list:][*:]item[/*:m:][/list:u:]',
+ ),
+ array(
+ 'Test default bbcodes: simple list-item closed',
+ '[list][*]item[/*][/list]',
+ '[list:][*:]item[/*:][/list:u:]',
+ ),
+ array(
+ 'Test default bbcodes: simple list-item numbered',
+ '[list=1][*]item[/list]',
+ '[list=1:][*:]item[/*:m:][/list:o:]',
+ ),
+ array(
+ 'Test default bbcodes: simple list-item alpha',
+ '[list=a][*]item[/list]',
+ '[list=a:][*:]item[/*:m:][/list:o:]',
+ ),
+ array(
+ 'Test default bbcodes: simple list-item roman',
+ '[list=i][*]item[/list]',
+ '[list=i:][*:]item[/*:m:][/list:o:]',
+ ),
+ array(
+ 'Test default bbcodes: simple list-item disc',
+ '[list=disc][*]item[/list]',
+ '[list=disc:][*:]item[/*:m:][/list:u:]',
+ ),
+ array(
+ 'Test default bbcodes: simple list-item circle',
+ '[list=circle][*]item[/list]',
+ '[list=circle:][*:]item[/*:m:][/list:u:]',
+ ),
+ array(
+ 'Test default bbcodes: simple list-item square',
+ '[list=square][*]item[/list]',
+ '[list=square:][*:]item[/*:m:][/list:u:]',
+ ),
+ array(
+ 'Test default bbcodes: simple img',
+ '[img]https://area51.phpbb.com/images/area51.png[/img]',
+ '[img:]https&#58;//area51&#46;phpbb&#46;com/images/area51&#46;png[/img:]',
+ ),
+ array(
+ 'Test default bbcodes: simple url',
+ '[url]https://area51.phpbb.com/[/url]',
+ '[url:]https&#58;//area51&#46;phpbb&#46;com/[/url:]',
+ ),
+ array(
+ 'Test default bbcodes: simple url with description',
+ '[url=https://area51.phpbb.com/]Area51[/url]',
+ '[url=https&#58;//area51&#46;phpbb&#46;com/:]Area51[/url:]',
+ ),
+ array(
+ 'Test default bbcodes: simple email',
+ '[email]bbcode-test@phpbb.com[/email]',
+ '[email:]bbcode-test@phpbb&#46;com[/email:]',
+ ),
+ array(
+ 'Test default bbcodes: simple email with description',
+ '[email=bbcode-test@phpbb.com]Email[/email]',
+ '[email=bbcode-test@phpbb&#46;com:]Email[/email:]',
+ ),
+ array(
+ 'Test default bbcodes: simple attachment',
+ '[attachment=0]filename[/attachment]',
+ '[attachment=0:]<!-- ia0 -->filename<!-- ia0 -->[/attachment:]',
+ ),
- $result = $parser->first_pass('[i]Italic [u]underlined text[/u][/i]');
- $result = $parser->second_pass($result);
+ // Special cases for quote which were reported as bugs before
+ array(
+ 'PHPBB3-1401 - correct: parsed',
+ '[quote=&quot;&#91;test]test&quot;]test [ test[/quote]',
+ '[quote=&quot;&#91;test]test&quot;:]test [ test[/quote:]',
+ ),
+ array(
+ 'PHPBB3-6117 - correct: parsed',
+ '[quote]test[/quote] test ] and [ test [quote]test[/quote]',
+ '[quote:]test[/quote:] test ] and [ test [quote:]test[/quote:]',
+ ),
+ array(
+ 'PHPBB3-6200 - correct: parsed',
+ '[quote=&quot;[&quot;]test[/quote]',
+ '[quote=&quot;&#91;&quot;:]test[/quote:]',
+ ),
+ array(
+ 'PHPBB3-9364 - quoted: "test[/[/b]quote] test" / non-quoted: "[/quote] test" - also failed if layout distorted',
+ '[quote]test[/[/b]quote] test [/quote][/quote] test',
+ '[quote:]test[/[/b]quote] test [/quote:][/quote] test',
+ ),
+ array(
+ 'PHPBB3-8096 - first quote tag parsed, second quote tag unparsed',
+ '[quote=&quot;a&quot;]a[/quote][quote=&quot;a]a[/quote]',
+ '[quote=&quot;a&quot;:]a[/quote:][quote=&quot;a]a[/quote]',
+ ),
- $expected = '<span style="font-style: italic">Italic <span style="text-decoration: underline">underlined text</span></span>';
+ // Simple bbcodes nesting
+ array(
+ 'Allow textual bbcodes in textual bbcodes',
+ '[b]bold [i]bold + italic[/i][/b]',
+ '[b:]bold [i:]bold + italic[/i:][/b:]',
+ ),
+ array(
+ 'Allow textual bbcodes in url with description',
+ '[url=https://area51.phpbb.com/]Area51 [i]italic[/i][/url]',
+ '[url=https&#58;//area51&#46;phpbb&#46;com/:]Area51 [i:]italic[/i:][/url:]',
+ ),
+ array(
+ 'Allow url with description in textual bbcodes',
+ '[i]italic [url=https://area51.phpbb.com/]Area51[/url][/i]',
+ '[i:]italic [url=https&#58;//area51&#46;phpbb&#46;com/:]Area51[/url:][/i:]',
+ ),
- $this->assertEquals($expected, $result, 'Simple nested BBCode first+second pass');
+ // Nesting bbcodes into quote usernames
+ array(
+ 'Allow textual bbcodes in usernames',
+ '[quote=&quot;[i]test[/i]&quot;]test[/quote]',
+ '[quote=&quot;[i:]test[/i:]&quot;:]test[/quote:]',
+ ),
+ array(
+ 'Allow links bbcodes in usernames',
+ '[quote=&quot;[url=https://area51.phpbb.com/]test[/url]&quot;]test[/quote]',
+ '[quote=&quot;[url=https&#58;//area51&#46;phpbb&#46;com/:]test[/url:]&quot;:]test[/quote:]',
+ ),
+ array(
+ 'Allow img bbcodes in usernames - Username displayed the image',
+ '[quote=&quot;[img]https://area51.phpbb.com/images/area51.png[/img]&quot;]test[/quote]',
+ '[quote=&quot;[img:]https&#58;//area51&#46;phpbb&#46;com/images/area51&#46;png[/img:]&quot;:]test[/quote:]',
+ ),
+ array(
+ 'Disallow flash bbcodes in usernames - Username displayed as [flash]http://www.phpbb.com/[/flash]',
+ '[quote=&quot;[flash]http://www.phpbb.com/[/flash]&quot;]test[/quote]',
+ '[quote=&quot;&#91;flash]http://www.phpbb.com/&#91;/flash]&quot;:]test[/quote:]',
+ ),
+ array(
+ 'Disallow quote bbcodes in usernames - Username displayed as [quote]test[/quote]',
+ '[quote=&quot;[quote]test[/quote]&quot;]test[/quote]',
+ '[quote=&quot;&#91;quote]test&#91;/quote]&quot;:]test[/quote:]',
+ ),
+
+ // Do not parse bbcodes in code boxes
+ array(
+ 'Do not parse textual bbcodes in code',
+ '[code]unparsed code [b]bold [i]bold + italic[/i][/b][/code]',
+ '[code:]unparsed code &#91;b&#93;bold &#91;i&#93;bold + italic&#91;/i&#93;&#91;/b&#93;[/code:]',
+ ),
+ array(
+ 'Do not parse quote bbcodes in code',
+ '[code]unparsed code [quote=&quot;username&quot;]quoted[/quote][/code]',
+ '[code:]unparsed code &#91;quote=&quot;username&quot;&#93;quoted&#91;/quote&#93;[/code:]',
+ ),
+
+ // New user friendly mixed nesting
+ array(
+ 'Textual bbcode nesting into textual bbcode',
+ '[b]bold [i]bold + italic[/b] italic[/i]',
+ '[b:]bold [i:]bold + italic[/b:] italic[/i:]',
+ 'Incomplete test case: secondpass parses as [b:]bold [i:]bold + italic[/i:] italic[/b:]',
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider bbcode_firstpass_data
+ */
+ public function test_bbcode_firstpass($description, $message, $expected, $incomplete = false)
+ {
+ if ($incomplete)
+ {
+ $this->markTestIncomplete($incomplete);
+ }
+
+ global $user, $request;
+ $user = new phpbb_mock_user;
+ $request = new phpbb_mock_request;
+
+ $bbcode = new bbcode_firstpass();
+ $bbcode->message = $message;
+ $bbcode->bbcode_init(false);
+ $bbcode->parse_bbcode();
+ $this->assertEquals($expected, $bbcode->message);
}
}
diff --git a/tests/compress/compress_test.php b/tests/compress/compress_test.php
index ac8dd358d3..6623f7ed8a 100644
--- a/tests/compress/compress_test.php
+++ b/tests/compress/compress_test.php
@@ -38,10 +38,16 @@ class phpbb_compress_test extends phpbb_test_case
$phpbb_root_path = '';
$this->path = dirname(__FILE__) . '/fixtures/';
+ }
- if (!@extension_loaded('zlib') || !@extension_loaded('bz2'))
+ protected function check_extensions($extensions)
+ {
+ foreach ($extensions as $extension)
{
- $this->markTestSkipped('PHP needs to be compiled with --with-zlib and --with-bz2 in order to run these tests');
+ if (!@extension_loaded($extension))
+ {
+ $this->markTestSkipped("$extension extension is not loaded");
+ }
}
}
@@ -114,17 +120,18 @@ class phpbb_compress_test extends phpbb_test_case
public function tar_archive_list()
{
return array(
- array('archive.tar', '.tar'),
- array('archive.tar.gz', '.tar.gz'),
- array('archive.tar.bz2', '.tar.bz2'),
+ array('archive.tar', '.tar', array()),
+ array('archive.tar.gz', '.tar.gz', array('zlib')),
+ array('archive.tar.bz2', '.tar.bz2', array('bz2')),
);
}
/**
* @dataProvider tar_archive_list
*/
- public function test_extract_tar($filename, $type)
+ public function test_extract_tar($filename, $type, $extensions)
{
+ $this->check_extensions($extensions);
$compress = new compress_tar('r', $this->path . $filename);
$compress->extract('tests/compress/' . self::EXTRACT_DIR);
$this->valid_extraction();
@@ -141,8 +148,10 @@ class phpbb_compress_test extends phpbb_test_case
* @depends test_extract_tar
* @dataProvider tar_archive_list
*/
- public function test_compress_tar($filename, $type)
+ public function test_compress_tar($filename, $type, $extensions)
{
+ $this->check_extensions($extensions);
+
$tar = dirname(__FILE__) . self::ARCHIVE_DIR . $filename;
$compress = new compress_tar('w', $tar);
$this->archive_files($compress);
@@ -160,6 +169,8 @@ class phpbb_compress_test extends phpbb_test_case
*/
public function test_compress_zip()
{
+ $this->check_extensions(array('zlib'));
+
$zip = dirname(__FILE__) . self::ARCHIVE_DIR . 'archive.zip';
$compress = new compress_zip('w', $zip);
$this->archive_files($compress);
diff --git a/tests/dbal/connect_test.php b/tests/dbal/connect_test.php
index 505ce28fa1..1e352d6b03 100644
--- a/tests/dbal/connect_test.php
+++ b/tests/dbal/connect_test.php
@@ -22,9 +22,7 @@ class phpbb_dbal_connect_test extends phpbb_database_test_case
$config = $this->get_database_config();
- require_once dirname(__FILE__) . '/../../phpBB/includes/db/' . $config['dbms'] . '.php';
- $dbal = 'dbal_' . $config['dbms'];
- $db = new $dbal();
+ $db = new $config['dbms']();
// Failure to connect results in a trigger_error call in dbal.
// phpunit converts triggered errors to exceptions.
diff --git a/tests/di/create_container_test.php b/tests/di/create_container_test.php
index c2b8a0fc0b..6de8803df9 100644
--- a/tests/di/create_container_test.php
+++ b/tests/di/create_container_test.php
@@ -9,7 +9,6 @@
require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php';
require_once dirname(__FILE__) . '/../../phpBB/includes/functions_container.php';
-require_once dirname(__FILE__) . '/../../phpBB/includes/db/dbal.php';
class phpbb_di_container_test extends phpbb_test_case
{
@@ -52,7 +51,7 @@ class phpbb_di_container_test extends phpbb_test_case
}
}
-class dbal_container_mock extends dbal
+class phpbb_db_driver_container_mock extends phpbb_db_driver
{
public function sql_connect()
{
diff --git a/tests/functional/acp_permissions_test.php b/tests/functional/acp_permissions_test.php
new file mode 100644
index 0000000000..f7fd44fc89
--- /dev/null
+++ b/tests/functional/acp_permissions_test.php
@@ -0,0 +1,128 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @group functional
+*/
+class phpbb_functional_acp_permissions_test extends phpbb_functional_test_case
+{
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->login();
+ $this->admin_login();
+ $this->add_lang('acp/permissions');
+ }
+
+ public function test_permissions_tab()
+ {
+ // Permissions tab
+ // XXX hardcoded id
+ $crawler = $this->request('GET', 'adm/index.php?i=16&sid=' . $this->sid);
+ $this->assert_response_success();
+ // these language strings are html
+ $this->assertContains($this->lang('ACP_PERMISSIONS_EXPLAIN'), $this->client->getResponse()->getContent());
+ }
+
+ public function test_select_user()
+ {
+ // User permissions
+ $crawler = $this->request('GET', 'adm/index.php?i=acp_permissions&icat=16&mode=setting_user_global&sid=' . $this->sid);
+ $this->assert_response_success();
+ $this->assertContains($this->lang('ACP_USERS_PERMISSIONS_EXPLAIN'), $this->client->getResponse()->getContent());
+
+ // Select admin
+ $form = $crawler->selectButton($this->lang('SUBMIT'))->form();
+ $data = array('username[0]' => 'admin');
+ $form->setValues($data);
+ $crawler = $this->client->submit($form);
+ $this->assert_response_success();
+ $this->assertContains($this->lang('ACL_SET'), $crawler->filter('h1')->eq(1)->text());
+ }
+
+ public function permissions_data()
+ {
+ return array(
+ // description
+ // permission type
+ // permission name
+ // mode
+ // object name
+ // object id
+ array(
+ 'user permission',
+ 'u_',
+ 'u_hideonline',
+ 'setting_user_global',
+ 'user_id',
+ 2,
+ ),
+ array(
+ 'moderator permission',
+ 'm_',
+ 'm_ban',
+ 'setting_mod_global',
+ 'group_id',
+ 4,
+ ),
+ /* Admin does not work yet, probably because founder can do everything
+ array(
+ 'admin permission',
+ 'a_',
+ 'a_forum',
+ 'setting_admin_global',
+ 'group_id',
+ 5,
+ ),
+ */
+ );
+ }
+
+ /**
+ * @dataProvider permissions_data
+ */
+ public function test_change_permission($description, $permission_type, $permission, $mode, $object_name, $object_id)
+ {
+ // Get the form
+ $crawler = $this->request('GET', "adm/index.php?i=acp_permissions&icat=16&mode=$mode&${object_name}[0]=$object_id&type=$permission_type&sid=" . $this->sid);
+ $this->assert_response_success();
+ $this->assertContains($this->lang('ACL_SET'), $crawler->filter('h1')->eq(1)->text());
+
+ // XXX globals for phpbb_auth, refactor it later
+ global $db, $cache;
+ $db = $this->get_db();
+ $cache = new phpbb_mock_null_cache;
+
+ $auth = new phpbb_auth;
+ // XXX hardcoded id
+ $user_data = $auth->obtain_user_data(2);
+ $auth->acl($user_data);
+ $this->assertEquals(1, $auth->acl_get($permission));
+
+ // Set u_hideonline to never
+ $form = $crawler->selectButton($this->lang('APPLY_PERMISSIONS'))->form();
+ // initially it should be a yes
+ $values = $form->getValues();
+ $this->assertEquals(1, $values["setting[$object_id][0][$permission]"]);
+ // set to never
+ $data = array("setting[$object_id][0][$permission]" => '0');
+ $form->setValues($data);
+ $crawler = $this->client->submit($form);
+ $this->assert_response_success();
+ $this->assertContains($this->lang('AUTH_UPDATED'), $crawler->text());
+
+ // check acl again
+ $auth = new phpbb_auth;
+ // XXX hardcoded id
+ $user_data = $auth->obtain_user_data(2);
+ $auth->acl($user_data);
+ $this->assertEquals(0, $auth->acl_get($permission));
+ }
+}
diff --git a/tests/functional/auth_test.php b/tests/functional/auth_test.php
index e955dcb4df..f92a4a2210 100644
--- a/tests/functional/auth_test.php
+++ b/tests/functional/auth_test.php
@@ -18,9 +18,19 @@ class phpbb_functional_auth_test extends phpbb_functional_test_case
// check for logout link
$crawler = $this->request('GET', 'index.php');
+ $this->assert_response_success();
$this->assertContains($this->lang('LOGOUT_USER', 'admin'), $crawler->filter('.navbar')->text());
}
+ public function test_login_other()
+ {
+ $this->create_user('anothertestuser');
+ $this->login('anothertestuser');
+ $crawler = $this->request('GET', 'index.php');
+ $this->assert_response_success();
+ $this->assertContains('anothertestuser', $crawler->filter('.icon-logout')->text());
+ }
+
/**
* @depends test_login
*/
@@ -31,10 +41,23 @@ class phpbb_functional_auth_test extends phpbb_functional_test_case
// logout
$crawler = $this->request('GET', 'ucp.php?sid=' . $this->sid . '&mode=logout');
+ $this->assert_response_success();
$this->assertContains($this->lang('LOGOUT_REDIRECT'), $crawler->filter('#message')->text());
// look for a register link, which should be visible only when logged out
$crawler = $this->request('GET', 'index.php');
+ $this->assert_response_success();
$this->assertContains($this->lang('REGISTER'), $crawler->filter('.navbar')->text());
}
+
+ public function test_acp_login()
+ {
+ $this->login();
+ $this->admin_login();
+
+ // check that we are logged in
+ $crawler = $this->request('GET', 'adm/index.php?sid=' . $this->sid);
+ $this->assert_response_success();
+ $this->assertContains($this->lang('ADMIN_PANEL'), $crawler->filter('h1')->text());
+ }
}
diff --git a/tests/functional/fileupload_form_test.php b/tests/functional/fileupload_form_test.php
index 99afcfdc3d..b663c89e95 100644
--- a/tests/functional/fileupload_form_test.php
+++ b/tests/functional/fileupload_form_test.php
@@ -44,8 +44,9 @@ class phpbb_functional_fileupload_form_test extends phpbb_functional_test_case
public function test_empty_file()
{
+ $this->markTestIncomplete('Test fails intermittently.');
$crawler = $this->upload_file('empty.png', 'image/png');
- $this->assertEquals($this->lang('ATTACHED_IMAGE_NOT_IMAGE'), $crawler->filter('div#message p')->text());
+ $this->assertEquals($this->lang('ATTACHED_IMAGE_NOT_IMAGE'), $this->assert_filter($crawler, 'div#message p')->text());
}
public function test_invalid_extension()
@@ -63,6 +64,7 @@ class phpbb_functional_fileupload_form_test extends phpbb_functional_test_case
public function test_valid_file()
{
+ $this->markTestIncomplete('Test fails intermittently.');
$crawler = $this->upload_file('valid.jpg', 'image/jpeg');
$this->assert_response_success();
// ensure there was no error message rendered
diff --git a/tests/functional/memberlist_test.php b/tests/functional/memberlist_test.php
new file mode 100644
index 0000000000..879bee2f0e
--- /dev/null
+++ b/tests/functional/memberlist_test.php
@@ -0,0 +1,43 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @group functional
+*/
+class phpbb_functional_memberlist_test extends phpbb_functional_test_case
+{
+ public function test_memberlist()
+ {
+ $this->create_user('memberlist-test-user');
+ // logs in as admin
+ $this->login();
+ $crawler = $this->request('GET', 'memberlist.php?sid=' . $this->sid);
+ $this->assert_response_success();
+ $this->assertContains('memberlist-test-user', $crawler->text());
+
+ // restrict by first character
+ $crawler = $this->request('GET', 'memberlist.php?first_char=m&sid=' . $this->sid);
+ $this->assert_response_success();
+ $this->assertContains('memberlist-test-user', $crawler->text());
+
+ // make sure results for wrong character are not returned
+ $crawler = $this->request('GET', 'memberlist.php?first_char=a&sid=' . $this->sid);
+ $this->assert_response_success();
+ $this->assertNotContains('memberlist-test-user', $crawler->text());
+ }
+
+ public function test_viewprofile()
+ {
+ $this->login();
+ // XXX hardcoded user id
+ $crawler = $this->request('GET', 'memberlist.php?mode=viewprofile&u=2&sid=' . $this->sid);
+ $this->assert_response_success();
+ $this->assertContains('admin', $crawler->filter('h2')->text());
+ }
+}
diff --git a/tests/functional/posting_test.php b/tests/functional/posting_test.php
index f54a3591b2..d05207edf0 100644
--- a/tests/functional/posting_test.php
+++ b/tests/functional/posting_test.php
@@ -15,70 +15,93 @@ class phpbb_functional_posting_test extends phpbb_functional_test_case
public function test_post_new_topic()
{
$this->login();
- $this->add_lang('posting');
- $crawler = $this->request('GET', 'posting.php?mode=post&f=2&sid=' . $this->sid);
- $this->assertContains($this->lang('POST_TOPIC'), $crawler->filter('html')->text());
+ // Test creating topic
+ $post = $this->create_topic(2, 'Test Topic 1', 'This is a test topic posted by the testing framework.');
- $hidden_fields = array();
- $hidden_fields[] = $crawler->filter('[type="hidden"]')->each(function ($node, $i) {
- return array('name' => $node->getAttribute('name'), 'value' => $node->getAttribute('value'));
- });
+ $crawler = $this->request('GET', "viewtopic.php?t={$post['topic_id']}&sid={$this->sid}");
+ $this->assertContains('This is a test topic posted by the testing framework.', $crawler->filter('html')->text());
- $test_message = 'This is a test topic posted by the testing framework.';
- $form_data = array(
- 'subject' => 'Test Topic 1',
- 'message' => $test_message,
- 'post' => true,
- 'f' => 2,
- 'mode' => 'post',
- 'sid' => $this->sid,
- );
+ // Test creating a reply
+ $post2 = $this->create_post(2, $post['topic_id'], 'Re: Test Topic 1', 'This is a test post posted by the testing framework.');
- foreach ($hidden_fields as $fields)
- {
- foreach($fields as $field)
- {
- $form_data[$field['name']] = $field['value'];
- }
- }
+ $crawler = $this->request('GET', "viewtopic.php?t={$post2['topic_id']}&sid={$this->sid}");
+ $this->assertContains('This is a test post posted by the testing framework.', $crawler->filter('html')->text());
- // Bypass time restriction that said that if the lastclick time (i.e. time when the form was opened)
- // is not at least 2 seconds before submission, cancel the form
- $form_data['lastclick'] = 0;
+ // Test quoting a message
+ $crawler = $this->request('GET', "posting.php?mode=quote&f=2&t={$post2['topic_id']}&p={$post2['post_id']}&sid={$this->sid}");
+ $this->assert_response_success();
+ $this->assertContains('This is a test post posted by the testing framework.', $crawler->filter('html')->text());
+ }
- // I use a request because the form submission method does not allow you to send data that is not
- // contained in one of the actual form fields that the browser sees (i.e. it ignores "hidden" inputs)
- // Instead, I send it as a request with the submit button "post" set to true.
- $crawler = $this->client->request('POST', 'posting.php', $form_data);
- $this->assertContains($this->lang('POST_STORED'), $crawler->filter('html')->text());
+ /**
+ * Creates a topic
+ *
+ * Be sure to login before creating
+ *
+ * @param int $forum_id
+ * @param string $subject
+ * @param string $message
+ * @param array $additional_form_data Any additional form data to be sent in the request
+ * @return array post_id, topic_id
+ */
+ public function create_topic($forum_id, $subject, $message, $additional_form_data = array())
+ {
+ $posting_url = "posting.php?mode=post&f={$forum_id}&sid={$this->sid}";
+
+ $form_data = array_merge(array(
+ 'subject' => $subject,
+ 'message' => $message,
+ 'post' => true,
+ ), $additional_form_data);
- $crawler = $this->request('GET', 'viewtopic.php?t=2&sid=' . $this->sid);
- $this->assertContains($test_message, $crawler->filter('html')->text());
+ return $this->submit_post($posting_url, 'POST_TOPIC', $form_data);
}
- public function test_post_reply()
+ /**
+ * Creates a post
+ *
+ * Be sure to login before creating
+ *
+ * @param int $forum_id
+ * @param string $subject
+ * @param string $message
+ * @param array $additional_form_data Any additional form data to be sent in the request
+ * @return array post_id, topic_id
+ */
+ public function create_post($forum_id, $topic_id, $subject, $message, $additional_form_data = array())
{
- $this->login();
- $this->add_lang('posting');
+ $posting_url = "posting.php?mode=reply&f={$forum_id}&t={$topic_id}&sid={$this->sid}";
- $crawler = $this->request('GET', 'posting.php?mode=reply&t=2&f=2&sid=' . $this->sid);
- $this->assertContains($this->lang('POST_REPLY'), $crawler->filter('html')->text());
+ $form_data = array_merge(array(
+ 'subject' => $subject,
+ 'message' => $message,
+ 'post' => true,
+ ), $additional_form_data);
- $hidden_fields = array();
- $hidden_fields[] = $crawler->filter('[type="hidden"]')->each(function ($node, $i) {
- return array('name' => $node->getAttribute('name'), 'value' => $node->getAttribute('value'));
- });
+ return $this->submit_post($posting_url, 'POST_REPLY', $form_data);
+ }
+
+ /**
+ * Helper for submitting posts
+ *
+ * @param string $posting_url
+ * @param string $posting_contains
+ * @param array $form_data
+ * @return array post_id, topic_id
+ */
+ protected function submit_post($posting_url, $posting_contains, $form_data)
+ {
+ $this->add_lang('posting');
- $test_message = 'This is a test post posted by the testing framework.';
- $form_data = array(
- 'subject' => 'Re: Test Topic 1',
- 'message' => $test_message,
- 'post' => true,
- 't' => 2,
- 'f' => 2,
- 'mode' => 'reply',
- 'sid' => $this->sid,
+ $crawler = $this->request('GET', $posting_url);
+ $this->assert_response_success();
+ $this->assertContains($this->lang($posting_contains), $crawler->filter('html')->text());
+
+ $hidden_fields = array(
+ $crawler->filter('[type="hidden"]')->each(function ($node, $i) {
+ return array('name' => $node->getAttribute('name'), 'value' => $node->getAttribute('value'));
+ }),
);
foreach ($hidden_fields as $fields)
@@ -89,14 +112,28 @@ class phpbb_functional_posting_test extends phpbb_functional_test_case
}
}
- // For reasoning behind the following command, see the test_post_new_topic() test
+ // Bypass time restriction that said that if the lastclick time (i.e. time when the form was opened)
+ // is not at least 2 seconds before submission, cancel the form
$form_data['lastclick'] = 0;
- // Submit the post
- $crawler = $this->client->request('POST', 'posting.php', $form_data);
+ // I use a request because the form submission method does not allow you to send data that is not
+ // contained in one of the actual form fields that the browser sees (i.e. it ignores "hidden" inputs)
+ // Instead, I send it as a request with the submit button "post" set to true.
+ $crawler = $this->client->request('POST', $posting_url, $form_data);
+ $this->assert_response_success();
$this->assertContains($this->lang('POST_STORED'), $crawler->filter('html')->text());
- $crawler = $this->request('GET', 'viewtopic.php?t=2&sid=' . $this->sid);
- $this->assertContains($test_message, $crawler->filter('html')->text());
+ $url = $crawler->selectLink($this->lang('VIEW_MESSAGE', '', ''))->link()->getUri();
+
+ $matches = $topic_id = $post_id = false;
+ preg_match_all('#&t=([0-9]+)(&p=([0-9]+))?#', $url, $matches);
+
+ $topic_id = (int) (isset($matches[1][0])) ? $matches[1][0] : 0;
+ $post_id = (int) (isset($matches[3][0])) ? $matches[3][0] : 0;
+
+ return array(
+ 'topic_id' => $topic_id,
+ 'post_id' => $post_id,
+ );
}
}
diff --git a/tests/functions/convert_30_dbms_to_31_test.php b/tests/functions/convert_30_dbms_to_31_test.php
new file mode 100644
index 0000000000..d08bf87f15
--- /dev/null
+++ b/tests/functions/convert_30_dbms_to_31_test.php
@@ -0,0 +1,40 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php';
+
+class phpbb_convert_30_dbms_to_31_test extends phpbb_test_case
+{
+ public function convert_30_dbms_to_31_data()
+ {
+ return array(
+ array('firebird'),
+ array('mssql'),
+ array('mssql_odbc'),
+ array('mssqlnative'),
+ array('mysql'),
+ array('mysqli'),
+ array('oracle'),
+ array('postgres'),
+ array('sqlite'),
+ );
+ }
+
+ /**
+ * @dataProvider convert_30_dbms_to_31_data
+ */
+ public function test_convert_30_dbms_to_31($input)
+ {
+ $expected = "phpbb_db_driver_$input";
+
+ $output = phpbb_convert_30_dbms_to_31($input);
+
+ $this->assertEquals($expected, $output);
+ }
+}
diff --git a/tests/functions_database_helper/fixtures/bookmarks_duplicates.xml b/tests/functions_database_helper/fixtures/bookmarks_duplicates.xml
new file mode 100644
index 0000000000..d49f76b073
--- /dev/null
+++ b/tests/functions_database_helper/fixtures/bookmarks_duplicates.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_bookmarks">
+ <column>user_id</column>
+ <column>topic_id</column>
+
+ <!-- one entry for this topic -->
+ <row>
+ <value>1</value>
+ <value>1</value>
+ </row>
+
+ <!-- non-conflicting entries -->
+ <row>
+ <value>2</value>
+ <value>2</value>
+ </row>
+ <row>
+ <value>3</value>
+ <value>3</value>
+ </row>
+
+ <!-- conflicting entries -->
+ <row>
+ <value>1</value>
+ <value>4</value>
+ </row>
+ <row>
+ <value>1</value>
+ <value>5</value>
+ </row>
+
+ <!-- conflicting and non-conflicting entries -->
+ <row>
+ <value>1</value>
+ <value>6</value>
+ </row>
+ <row>
+ <value>1</value>
+ <value>7</value>
+ </row>
+ <row>
+ <value>2</value>
+ <value>6</value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/functions_database_helper/fixtures/topics_watch_duplicates.xml b/tests/functions_database_helper/fixtures/topics_watch_duplicates.xml
new file mode 100644
index 0000000000..c387bb737a
--- /dev/null
+++ b/tests/functions_database_helper/fixtures/topics_watch_duplicates.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_topics_watch">
+ <column>user_id</column>
+ <column>topic_id</column>
+ <column>notify_status</column>
+
+ <!-- one entry for this topic -->
+ <row>
+ <value>1</value>
+ <value>1</value>
+ <value>1</value>
+ </row>
+
+ <!-- non-conflicting entries -->
+ <row>
+ <value>1</value>
+ <value>2</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>2</value>
+ <value>3</value>
+ <value>0</value>
+ </row>
+
+ <!-- conflicting entries, same notify status -->
+ <row>
+ <value>1</value>
+ <value>4</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>1</value>
+ <value>5</value>
+ <value>1</value>
+ </row>
+
+ <!-- conflicting entries, notify status 0 into 1 -->
+ <row>
+ <value>1</value>
+ <value>6</value>
+ <value>0</value>
+ </row>
+ <row>
+ <value>1</value>
+ <value>7</value>
+ <value>1</value>
+ </row>
+
+ <!-- conflicting entries, notify status 1 into 0 -->
+ <row>
+ <value>1</value>
+ <value>8</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>1</value>
+ <value>9</value>
+ <value>0</value>
+ </row>
+
+ <!-- conflicting and non-conflicting entries -->
+ <row>
+ <value>1</value>
+ <value>10</value>
+ <value>0</value>
+ </row>
+ <row>
+ <value>1</value>
+ <value>11</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>2</value>
+ <value>10</value>
+ <value>1</value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/functions_database_helper/update_rows_avoiding_duplicates_notify_status_test.php b/tests/functions_database_helper/update_rows_avoiding_duplicates_notify_status_test.php
new file mode 100644
index 0000000000..d4881daf7e
--- /dev/null
+++ b/tests/functions_database_helper/update_rows_avoiding_duplicates_notify_status_test.php
@@ -0,0 +1,101 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/../../phpBB/includes/functions_database_helper.php';
+
+class phpbb_update_rows_avoiding_duplicates_notify_status_test extends phpbb_database_test_case
+{
+ public function getDataSet()
+ {
+ return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/topics_watch_duplicates.xml');
+ }
+
+ public static function fixture_data()
+ {
+ return array(
+ // description
+ // from array
+ // to value
+ // expected count with to value post update
+ // expected notify_status values
+ array(
+ 'trivial',
+ array(1),
+ 1000,
+ 1,
+ 1,
+ ),
+ array(
+ 'no conflict',
+ array(2),
+ 3,
+ 2,
+ 1,
+ ),
+ array(
+ 'conflict, same notify status',
+ array(4),
+ 5,
+ 1,
+ 1,
+ ),
+ array(
+ 'conflict, notify status 0 into 1',
+ array(6),
+ 7,
+ 1,
+ 0,
+ ),
+ array(
+ 'conflict, notify status 1 into 0',
+ array(8),
+ 9,
+ 1,
+ 0,
+ ),
+ array(
+ 'conflict and no conflict',
+ array(10),
+ 11,
+ 2,
+ 0,
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider fixture_data
+ */
+ public function test_update($description, $from, $to, $expected_result_count, $expected_notify_status)
+ {
+ $db = $this->new_dbal();
+
+ phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $from, $to);
+
+ $sql = 'SELECT COUNT(*) AS remaining_rows
+ FROM ' . TOPICS_WATCH_TABLE . '
+ WHERE topic_id = ' . (int) $to;
+ $result = $db->sql_query($sql);
+ $result_count = $db->sql_fetchfield('remaining_rows');
+ $db->sql_freeresult($result);
+
+ $this->assertEquals($expected_result_count, $result_count);
+
+ // user id of 1 is the user being updated
+ $sql = 'SELECT notify_status
+ FROM ' . TOPICS_WATCH_TABLE . '
+ WHERE topic_id = ' . (int) $to . '
+ AND user_id = 1';
+ $result = $db->sql_query($sql);
+ $notify_status = $db->sql_fetchfield('notify_status');
+ $db->sql_freeresult($result);
+
+ $this->assertEquals($expected_notify_status, $notify_status);
+ }
+}
diff --git a/tests/functions_database_helper/update_rows_avoiding_duplicates_test.php b/tests/functions_database_helper/update_rows_avoiding_duplicates_test.php
new file mode 100644
index 0000000000..2f01d29d15
--- /dev/null
+++ b/tests/functions_database_helper/update_rows_avoiding_duplicates_test.php
@@ -0,0 +1,71 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/../../phpBB/includes/functions_database_helper.php';
+
+class phpbb_update_rows_avoiding_duplicates_test extends phpbb_database_test_case
+{
+ public function getDataSet()
+ {
+ return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/bookmarks_duplicates.xml');
+ }
+
+ public static function fixture_data()
+ {
+ return array(
+ // description
+ // from array
+ // to value
+ // expected count with to value post update
+ array(
+ 'trivial',
+ array(1),
+ 10,
+ 1,
+ ),
+ array(
+ 'no conflict',
+ array(2),
+ 3,
+ 2,
+ ),
+ array(
+ 'conflict',
+ array(4),
+ 5,
+ 1,
+ ),
+ array(
+ 'conflict and no conflict',
+ array(6),
+ 7,
+ 2,
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider fixture_data
+ */
+ public function test_update($description, $from, $to, $expected_result_count)
+ {
+ $db = $this->new_dbal();
+
+ phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', $from, $to);
+
+ $sql = 'SELECT COUNT(*) AS remaining_rows
+ FROM ' . BOOKMARKS_TABLE . '
+ WHERE topic_id = ' . (int) $to;
+ $result = $db->sql_query($sql);
+ $result_count = $db->sql_fetchfield('remaining_rows');
+ $db->sql_freeresult($result);
+
+ $this->assertEquals($expected_result_count, $result_count);
+ }
+}
diff --git a/tests/mock/cache.php b/tests/mock/cache.php
index bc18ca066b..71af3037f5 100644
--- a/tests/mock/cache.php
+++ b/tests/mock/cache.php
@@ -121,7 +121,11 @@ class phpbb_mock_cache implements phpbb_cache_driver_interface
public function sql_load($query)
{
}
- public function sql_save($query, $query_result, $ttl)
+
+ /**
+ * {@inheritDoc}
+ */
+ public function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl)
{
return $query_result;
}
diff --git a/tests/mock/filesystem_extension_manager.php b/tests/mock/filesystem_extension_manager.php
new file mode 100644
index 0000000000..c5a51bbb3f
--- /dev/null
+++ b/tests/mock/filesystem_extension_manager.php
@@ -0,0 +1,32 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+class phpbb_mock_filesystem_extension_manager extends phpbb_mock_extension_manager
+{
+ public function __construct($phpbb_root_path)
+ {
+ $extensions = array();
+ $iterator = new DirectoryIterator($phpbb_root_path . 'ext/');
+ foreach ($iterator as $fileinfo)
+ {
+ if ($fileinfo->isDir() && substr($fileinfo->getFilename(), 0, 1) != '.')
+ {
+ $name = $fileinfo->getFilename();
+ $extension = array(
+ 'ext_name' => $name,
+ 'ext_active' => true,
+ 'ext_path' => 'ext/' . $name . '/',
+ );
+ $extensions[$name] = $extension;
+ }
+ }
+ ksort($extensions);
+ parent::__construct($phpbb_root_path, $extensions);
+ }
+}
diff --git a/tests/mock/fileupload.php b/tests/mock/fileupload.php
index 409036ba63..cbcbf4a6ab 100644
--- a/tests/mock/fileupload.php
+++ b/tests/mock/fileupload.php
@@ -20,33 +20,4 @@ class phpbb_mock_fileupload
{
return true;
}
-
- /**
- * Copied verbatim from phpBB/includes/functions_upload.php's fileupload
- * class to ensure the correct behaviour of filespec::move_file.
- *
- * Maps file extensions to the constant in second index of the array
- * returned by getimagesize()
- */
- public function image_types()
- {
- return array(
- IMAGETYPE_GIF => array('gif'),
- IMAGETYPE_JPEG => array('jpg', 'jpeg'),
- IMAGETYPE_PNG => array('png'),
- IMAGETYPE_SWF => array('swf'),
- IMAGETYPE_PSD => array('psd'),
- IMAGETYPE_BMP => array('bmp'),
- IMAGETYPE_TIFF_II => array('tif', 'tiff'),
- IMAGETYPE_TIFF_MM => array('tif', 'tiff'),
- IMAGETYPE_JPC => array('jpg', 'jpeg'),
- IMAGETYPE_JP2 => array('jpg', 'jpeg'),
- IMAGETYPE_JPX => array('jpg', 'jpeg'),
- IMAGETYPE_JB2 => array('jpg', 'jpeg'),
- IMAGETYPE_SWC => array('swc'),
- IMAGETYPE_IFF => array('iff'),
- IMAGETYPE_WBMP => array('wbmp'),
- IMAGETYPE_XBM => array('xbm'),
- );
- }
}
diff --git a/tests/mock/null_cache.php b/tests/mock/null_cache.php
new file mode 100644
index 0000000000..7bd33b441b
--- /dev/null
+++ b/tests/mock/null_cache.php
@@ -0,0 +1,47 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+class phpbb_mock_null_cache
+{
+ public function __construct()
+ {
+ }
+
+ public function get($var_name)
+ {
+ return false;
+ }
+
+ public function put($var_name, $var, $ttl = 0)
+ {
+ }
+
+ public function destroy($var_name, $table = '')
+ {
+ }
+
+ public function obtain_bots()
+ {
+ return array();
+ }
+
+ public function obtain_word_list()
+ {
+ return array();
+ }
+
+ public function set_bots($bots)
+ {
+ }
+
+ public function sql_exists($query_id)
+ {
+ return false;
+ }
+}
diff --git a/tests/session/append_sid_test.php b/tests/session/append_sid_test.php
index 34f6dea8ca..b9e9ac1aa9 100644
--- a/tests/session/append_sid_test.php
+++ b/tests/session/append_sid_test.php
@@ -1,54 +1,54 @@
-<?php
-/**
-*
-* @package testing
-* @copyright (c) 2011 phpBB Group
-* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
-*
-*/
-
-require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php';
-
-class phpbb_session_append_sid_test extends phpbb_test_case
-{
-
- public function append_sid_data()
- {
- return array(
- array('viewtopic.php?t=1&amp;f=2', false, true, false, 'viewtopic.php?t=1&amp;f=2', 'parameters in url-argument'),
- array('viewtopic.php', 't=1&amp;f=2', true, false, 'viewtopic.php?t=1&amp;f=2', 'parameters in params-argument using amp'),
- array('viewtopic.php', 't=1&f=2', false, false, 'viewtopic.php?t=1&f=2', 'parameters in params-argument using &'),
- array('viewtopic.php', array('t' => 1, 'f' => 2), true, false, 'viewtopic.php?t=1&amp;f=2', 'parameters in params-argument as array'),
-
- // Custom sid parameter
- array('viewtopic.php', 't=1&amp;f=2', true, 'custom-sid', 'viewtopic.php?t=1&amp;f=2&amp;sid=custom-sid', 'using session_id'),
-
- // Testing anchors
- array('viewtopic.php?t=1&amp;f=2#anchor', false, true, false, 'viewtopic.php?t=1&amp;f=2#anchor', 'anchor in url-argument'),
- array('viewtopic.php', 't=1&amp;f=2#anchor', true, false, 'viewtopic.php?t=1&amp;f=2#anchor', 'anchor in params-argument'),
- array('viewtopic.php', array('t' => 1, 'f' => 2, '#' => 'anchor'), true, false, 'viewtopic.php?t=1&amp;f=2#anchor', 'anchor in params-argument (array)'),
-
- // Anchors and custom sid
- array('viewtopic.php?t=1&amp;f=2#anchor', false, true, 'custom-sid', 'viewtopic.php?t=1&amp;f=2&amp;sid=custom-sid#anchor', 'anchor in url-argument using session_id'),
- array('viewtopic.php', 't=1&amp;f=2#anchor', true, 'custom-sid', 'viewtopic.php?t=1&amp;f=2&amp;sid=custom-sid#anchor', 'anchor in params-argument using session_id'),
- array('viewtopic.php', array('t' => 1, 'f' => 2, '#' => 'anchor'), true, 'custom-sid', 'viewtopic.php?t=1&amp;f=2&amp;sid=custom-sid#anchor', 'anchor in params-argument (array) using session_id'),
-
- // Empty parameters should not append the ?
- array('viewtopic.php', false, true, false, 'viewtopic.php', 'no params using bool false'),
- array('viewtopic.php', '', true, false, 'viewtopic.php', 'no params using empty string'),
- array('viewtopic.php', array(), true, false, 'viewtopic.php', 'no params using empty array'),
- );
- }
-
- /**
- * @dataProvider append_sid_data
- */
- public function test_append_sid($url, $params, $is_amp, $session_id, $expected, $description)
- {
- global $phpbb_dispatcher;
-
- $phpbb_dispatcher = new phpbb_mock_event_dispatcher;
- $this->assertEquals($expected, append_sid($url, $params, $is_amp, $session_id));
- }
-}
-
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2011 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php';
+
+class phpbb_session_append_sid_test extends phpbb_test_case
+{
+
+ public function append_sid_data()
+ {
+ return array(
+ array('viewtopic.php?t=1&amp;f=2', false, true, false, 'viewtopic.php?t=1&amp;f=2', 'parameters in url-argument'),
+ array('viewtopic.php', 't=1&amp;f=2', true, false, 'viewtopic.php?t=1&amp;f=2', 'parameters in params-argument using amp'),
+ array('viewtopic.php', 't=1&f=2', false, false, 'viewtopic.php?t=1&f=2', 'parameters in params-argument using &'),
+ array('viewtopic.php', array('t' => 1, 'f' => 2), true, false, 'viewtopic.php?t=1&amp;f=2', 'parameters in params-argument as array'),
+
+ // Custom sid parameter
+ array('viewtopic.php', 't=1&amp;f=2', true, 'custom-sid', 'viewtopic.php?t=1&amp;f=2&amp;sid=custom-sid', 'using session_id'),
+
+ // Testing anchors
+ array('viewtopic.php?t=1&amp;f=2#anchor', false, true, false, 'viewtopic.php?t=1&amp;f=2#anchor', 'anchor in url-argument'),
+ array('viewtopic.php', 't=1&amp;f=2#anchor', true, false, 'viewtopic.php?t=1&amp;f=2#anchor', 'anchor in params-argument'),
+ array('viewtopic.php', array('t' => 1, 'f' => 2, '#' => 'anchor'), true, false, 'viewtopic.php?t=1&amp;f=2#anchor', 'anchor in params-argument (array)'),
+
+ // Anchors and custom sid
+ array('viewtopic.php?t=1&amp;f=2#anchor', false, true, 'custom-sid', 'viewtopic.php?t=1&amp;f=2&amp;sid=custom-sid#anchor', 'anchor in url-argument using session_id'),
+ array('viewtopic.php', 't=1&amp;f=2#anchor', true, 'custom-sid', 'viewtopic.php?t=1&amp;f=2&amp;sid=custom-sid#anchor', 'anchor in params-argument using session_id'),
+ array('viewtopic.php', array('t' => 1, 'f' => 2, '#' => 'anchor'), true, 'custom-sid', 'viewtopic.php?t=1&amp;f=2&amp;sid=custom-sid#anchor', 'anchor in params-argument (array) using session_id'),
+
+ // Empty parameters should not append the ?
+ array('viewtopic.php', false, true, false, 'viewtopic.php', 'no params using bool false'),
+ array('viewtopic.php', '', true, false, 'viewtopic.php', 'no params using empty string'),
+ array('viewtopic.php', array(), true, false, 'viewtopic.php', 'no params using empty array'),
+ );
+ }
+
+ /**
+ * @dataProvider append_sid_data
+ */
+ public function test_append_sid($url, $params, $is_amp, $session_id, $expected, $description)
+ {
+ global $phpbb_dispatcher;
+
+ $phpbb_dispatcher = new phpbb_mock_event_dispatcher;
+ $this->assertEquals($expected, append_sid($url, $params, $is_amp, $session_id));
+ }
+}
+
diff --git a/tests/session/testable_factory.php b/tests/session/testable_factory.php
index cb85a01c5c..1e2b194ece 100644
--- a/tests/session/testable_factory.php
+++ b/tests/session/testable_factory.php
@@ -59,10 +59,10 @@ class phpbb_session_testable_factory
/**
* Retrieve the configured session class instance
*
- * @param dbal $dbal The database connection to use for session data
+ * @param phpbb_db_driver $dbal The database connection to use for session data
* @return phpbb_mock_session_testable A session instance
*/
- public function get_session(dbal $dbal)
+ public function get_session(phpbb_db_driver $dbal)
{
// set up all the global variables used by session
global $SID, $_SID, $db, $config, $cache, $request;
diff --git a/tests/template/datasets/event_inheritance/ext/kappa/styles/all/template/test.html b/tests/template/datasets/event_inheritance/ext/kappa/styles/all/template/test.html
new file mode 100644
index 0000000000..3eb906a09e
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/ext/kappa/styles/all/template/test.html
@@ -0,0 +1 @@
+Kappa test event in all
diff --git a/tests/template/datasets/event_inheritance/ext/kappa/styles/silver/template/test.html b/tests/template/datasets/event_inheritance/ext/kappa/styles/silver/template/test.html
new file mode 100644
index 0000000000..3b65d80a6d
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/ext/kappa/styles/silver/template/test.html
@@ -0,0 +1 @@
+Kappa test event in silver
diff --git a/tests/template/datasets/event_inheritance/ext/kappa/styles/silver_inherit/template/test.html b/tests/template/datasets/event_inheritance/ext/kappa/styles/silver_inherit/template/test.html
new file mode 100644
index 0000000000..26826d59e3
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/ext/kappa/styles/silver_inherit/template/test.html
@@ -0,0 +1 @@
+Kappa test event in silver_inherit
diff --git a/tests/template/datasets/event_inheritance/ext/omega/styles/all/template/test.html b/tests/template/datasets/event_inheritance/ext/omega/styles/all/template/test.html
new file mode 100644
index 0000000000..003d193dc3
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/ext/omega/styles/all/template/test.html
@@ -0,0 +1 @@
+Omega test event in all
diff --git a/tests/template/datasets/event_inheritance/ext/omega/styles/silver/template/test.html b/tests/template/datasets/event_inheritance/ext/omega/styles/silver/template/test.html
new file mode 100644
index 0000000000..6bf06f5457
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/ext/omega/styles/silver/template/test.html
@@ -0,0 +1 @@
+Omega test event in silver
diff --git a/tests/template/datasets/event_inheritance/ext/omega/styles/silver/template/two.html b/tests/template/datasets/event_inheritance/ext/omega/styles/silver/template/two.html
new file mode 100644
index 0000000000..7f8058f4e4
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/ext/omega/styles/silver/template/two.html
@@ -0,0 +1 @@
+two in silver in omega
diff --git a/tests/template/datasets/event_inheritance/ext/zeta/styles/all/template/test.html b/tests/template/datasets/event_inheritance/ext/zeta/styles/all/template/test.html
new file mode 100644
index 0000000000..5fc7e5ac12
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/ext/zeta/styles/all/template/test.html
@@ -0,0 +1 @@
+Zeta test event in all
diff --git a/tests/template/datasets/event_inheritance/styles/silver/template/event_test.html b/tests/template/datasets/event_inheritance/styles/silver/template/event_test.html
new file mode 100644
index 0000000000..4d78dddb12
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/styles/silver/template/event_test.html
@@ -0,0 +1 @@
+<!-- EVENT test -->
diff --git a/tests/template/datasets/event_inheritance/styles/silver/template/event_two.html b/tests/template/datasets/event_inheritance/styles/silver/template/event_two.html
new file mode 100644
index 0000000000..fe46be3782
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/styles/silver/template/event_two.html
@@ -0,0 +1 @@
+<!-- EVENT two -->
diff --git a/tests/template/datasets/event_inheritance/styles/silver_inherit/template/event_test.html b/tests/template/datasets/event_inheritance/styles/silver_inherit/template/event_test.html
new file mode 100644
index 0000000000..4d78dddb12
--- /dev/null
+++ b/tests/template/datasets/event_inheritance/styles/silver_inherit/template/event_test.html
@@ -0,0 +1 @@
+<!-- EVENT test -->
diff --git a/tests/template/datasets/ext_trivial/ext/trivial/styles/all/template/universal.html b/tests/template/datasets/ext_trivial/ext/trivial/styles/all/template/universal.html
new file mode 100644
index 0000000000..f2c5762ade
--- /dev/null
+++ b/tests/template/datasets/ext_trivial/ext/trivial/styles/all/template/universal.html
@@ -0,0 +1 @@
+Universal in trivial extension.
diff --git a/tests/template/datasets/ext_trivial/ext/trivial/styles/silver/template/simple.html b/tests/template/datasets/ext_trivial/ext/trivial/styles/silver/template/simple.html
new file mode 100644
index 0000000000..fe32a1ed3f
--- /dev/null
+++ b/tests/template/datasets/ext_trivial/ext/trivial/styles/silver/template/simple.html
@@ -0,0 +1 @@
+Simple in trivial extension.
diff --git a/tests/template/datasets/ext_trivial/styles/silver/template/event_simple.html b/tests/template/datasets/ext_trivial/styles/silver/template/event_simple.html
new file mode 100644
index 0000000000..604c1acdce
--- /dev/null
+++ b/tests/template/datasets/ext_trivial/styles/silver/template/event_simple.html
@@ -0,0 +1 @@
+<!-- EVENT simple -->
diff --git a/tests/template/datasets/ext_trivial/styles/silver/template/event_universal.html b/tests/template/datasets/ext_trivial/styles/silver/template/event_universal.html
new file mode 100644
index 0000000000..15425cacc3
--- /dev/null
+++ b/tests/template/datasets/ext_trivial/styles/silver/template/event_universal.html
@@ -0,0 +1 @@
+<!-- EVENT universal -->
diff --git a/tests/template/includephp_test.php b/tests/template/includephp_test.php
index c93a53e2ad..f1012b6939 100644
--- a/tests/template/includephp_test.php
+++ b/tests/template/includephp_test.php
@@ -48,7 +48,7 @@ class phpbb_template_includephp_test extends phpbb_template_template_test_case
$this->setup_engine(array('tpl_allow_php' => true));
- $this->style->set_custom_style('tests', $cache_dir, '');
+ $this->style->set_custom_style('tests', $cache_dir, array(), '');
$cache_file = $this->template->cachepath . 'includephp_absolute.html.php';
$this->run_template('includephp_absolute.html', array(), array(), array(), "Path is absolute.\ntesting included php", $cache_file);
diff --git a/tests/template/template_compile_test.php b/tests/template/template_compile_test.php
index 0cfcd6ceb5..7393fc1747 100644
--- a/tests/template/template_compile_test.php
+++ b/tests/template/template_compile_test.php
@@ -16,7 +16,7 @@ class phpbb_template_template_compile_test extends phpbb_test_case
protected function setUp()
{
- $this->template_compile = new phpbb_template_compile(false, null, '');
+ $this->template_compile = new phpbb_template_compile(false, null, $this->style_resource_locator, '');
$this->template_path = dirname(__FILE__) . '/templates';
}
diff --git a/tests/template/template_events_test.php b/tests/template/template_events_test.php
new file mode 100644
index 0000000000..6cea9b92e3
--- /dev/null
+++ b/tests/template/template_events_test.php
@@ -0,0 +1,118 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2011 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/template_test_case.php';
+
+class phpbb_template_template_events_test extends phpbb_template_template_test_case
+{
+ public function template_data()
+ {
+ return array(
+ /*
+ array(
+ '', // file
+ '', // dataset
+ array(), // style names
+ array(), // vars
+ array(), // block vars
+ array(), // destroy
+ '', // expected result
+ ),
+ */
+ array(
+ 'Simple template event',
+ 'ext_trivial',
+ array(),
+ 'event_simple.html',
+ array(),
+ array(),
+ array(),
+ "Simple in trivial extension.",
+ ),
+ array(
+ 'Universal template event ("all" style)',
+ 'ext_trivial',
+ array(),
+ 'event_universal.html',
+ array(),
+ array(),
+ array(),
+ "Universal in trivial extension.",
+ ),
+ array(
+ 'Template event with inheritance - parent',
+ 'event_inheritance',
+ array('silver'),
+ 'event_test.html',
+ array(),
+ array(),
+ array(),
+'Kappa test event in all
+Omega test event in all
+Zeta test event in all
+Kappa test event in silver
+Omega test event in silver',
+ ),
+ array(
+ 'Template event with inheritance - child',
+ 'event_inheritance',
+ array('silver_inherit', 'silver'),
+ 'event_test.html',
+ array(),
+ array(),
+ array(),
+'Kappa test event in all
+Omega test event in all
+Zeta test event in all
+Kappa test event in silver_inherit',
+ ),
+ array(
+ 'Definition in parent style',
+ 'event_inheritance',
+ array('silver_inherit', 'silver'),
+ 'event_two.html',
+ array(),
+ array(),
+ array(),
+'two in silver in omega',
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider template_data
+ */
+ public function test_event($desc, $dataset, $style_names, $file, array $vars, array $block_vars, array $destroy, $expected)
+ {
+ // Reset the engine state
+ $this->setup_engine_for_events($dataset, $style_names);
+
+ // Run test
+ $cache_file = $this->template->cachepath . str_replace('/', '.', $file) . '.php';
+ $this->run_template($file, $vars, $block_vars, $destroy, $expected, $cache_file);
+ }
+
+ protected function setup_engine_for_events($dataset, $style_names, array $new_config = array())
+ {
+ global $phpbb_root_path, $phpEx, $user;
+
+ $defaults = $this->config_defaults();
+ $config = new phpbb_config(array_merge($defaults, $new_config));
+
+ $this->template_path = dirname(__FILE__) . "/datasets/$dataset/styles/silver/template";
+ $this->style_resource_locator = new phpbb_style_resource_locator();
+ $this->extension_manager = new phpbb_mock_filesystem_extension_manager(
+ dirname(__FILE__) . "/datasets/$dataset/"
+ );
+ $this->template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $this->style_resource_locator, new phpbb_template_context, $this->extension_manager);
+ $this->style_provider = new phpbb_style_path_provider();
+ $this->style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $this->style_resource_locator, $this->style_provider, $this->template);
+ $this->style->set_custom_style('silver', array($this->template_path), $style_names, '');
+ }
+}
diff --git a/tests/template/template_test_case.php b/tests/template/template_test_case.php
index 2e6f703eb1..3c997cb00e 100644
--- a/tests/template/template_test_case.php
+++ b/tests/template/template_test_case.php
@@ -69,7 +69,7 @@ class phpbb_template_template_test_case extends phpbb_test_case
$this->style_provider = new phpbb_style_path_provider();
$this->template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $this->style_resource_locator, new phpbb_template_context());
$this->style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $this->style_resource_locator, $this->style_provider, $this->template);
- $this->style->set_custom_style('tests', $this->template_path, '');
+ $this->style->set_custom_style('tests', $this->template_path, array(), '');
}
protected function setUp()
diff --git a/tests/template/template_test_case_with_tree.php b/tests/template/template_test_case_with_tree.php
index 6a226f317a..7585be5728 100644
--- a/tests/template/template_test_case_with_tree.php
+++ b/tests/template/template_test_case_with_tree.php
@@ -24,6 +24,6 @@ class phpbb_template_template_test_case_with_tree extends phpbb_template_templat
$this->style_provider = new phpbb_style_path_provider();
$this->template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $this->style_resource_locator, new phpbb_template_context());
$this->style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $this->style_resource_locator, $this->style_provider, $this->template);
- $this->style->set_custom_style('tests', array($this->template_path, $this->parent_template_path), '');
+ $this->style->set_custom_style('tests', array($this->template_path, $this->parent_template_path), array(), '');
}
}
diff --git a/tests/template/templates/events.html b/tests/template/templates/events.html
new file mode 100644
index 0000000000..c44a7469e7
--- /dev/null
+++ b/tests/template/templates/events.html
@@ -0,0 +1,4 @@
+<!-- EVENT child_only -->
+<!-- EVENT parent_only -->
+<!-- EVENT parent_and_child -->
+<!-- EVENT random_event -->
diff --git a/tests/test_framework/phpbb_database_test_case.php b/tests/test_framework/phpbb_database_test_case.php
index b5076b92e9..72bce2e38a 100644
--- a/tests/test_framework/phpbb_database_test_case.php
+++ b/tests/test_framework/phpbb_database_test_case.php
@@ -49,7 +49,7 @@ abstract class phpbb_database_test_case extends PHPUnit_Extensions_Database_Test
$db_config = $this->get_database_config();
// Firebird requires table and column names to be uppercase
- if ($db_config['dbms'] == 'firebird')
+ if ($db_config['dbms'] == 'phpbb_db_driver_firebird')
{
$xml_data = file_get_contents($path);
$xml_data = preg_replace_callback('/(?:(<table name="))([a-z_]+)(?:(">))/', 'phpbb_database_test_case::to_upper', $xml_data);
@@ -118,9 +118,7 @@ abstract class phpbb_database_test_case extends PHPUnit_Extensions_Database_Test
$config = $this->get_database_config();
- require_once dirname(__FILE__) . '/../../phpBB/includes/db/' . $config['dbms'] . '.php';
- $dbal = 'dbal_' . $config['dbms'];
- $db = new $dbal();
+ $db = new $config['dbms']();
$db->sql_connect($config['dbhost'], $config['dbuser'], $config['dbpasswd'], $config['dbname'], $config['dbport']);
return $db;
diff --git a/tests/test_framework/phpbb_database_test_connection_manager.php b/tests/test_framework/phpbb_database_test_connection_manager.php
index d7c2804aa7..03097a10a0 100644
--- a/tests/test_framework/phpbb_database_test_connection_manager.php
+++ b/tests/test_framework/phpbb_database_test_connection_manager.php
@@ -108,7 +108,7 @@ class phpbb_database_test_connection_manager
// These require different connection strings on the phpBB side than they do in PDO
// so you must provide a DSN string for ODBC separately
- if (!empty($this->config['custom_dsn']) && ($this->config['dbms'] == 'mssql' || $this->config['dbms'] == 'firebird'))
+ if (!empty($this->config['custom_dsn']) && ($this->config['dbms'] == 'phpbb_db_driver_mssql' || $this->config['dbms'] == 'phpbb_db_driver_firebird'))
{
$dsn = 'odbc:' . $this->config['custom_dsn'];
}
@@ -117,12 +117,12 @@ class phpbb_database_test_connection_manager
{
switch ($this->config['dbms'])
{
- case 'mssql':
- case 'mssql_odbc':
+ case 'phpbb_db_driver_mssql':
+ case 'phpbb_db_driver_mssql_odbc':
$this->pdo = new phpbb_database_connection_odbc_pdo_wrapper('mssql', 0, $dsn, $this->config['dbuser'], $this->config['dbpasswd']);
break;
- case 'firebird':
+ case 'phpbb_db_driver_firebird':
if (!empty($this->config['custom_dsn']))
{
$this->pdo = new phpbb_database_connection_odbc_pdo_wrapper('firebird', 0, $dsn, $this->config['dbuser'], $this->config['dbpasswd']);
@@ -165,8 +165,8 @@ class phpbb_database_test_connection_manager
{
switch ($this->config['dbms'])
{
- case 'sqlite':
- case 'firebird':
+ case 'phpbb_db_driver_sqlite':
+ case 'phpbb_db_driver_firebird':
$this->connect();
// Drop all of the tables
foreach ($this->get_tables() as $table)
@@ -176,7 +176,7 @@ class phpbb_database_test_connection_manager
$this->purge_extras();
break;
- case 'oracle':
+ case 'phpbb_db_driver_oracle':
$this->connect();
// Drop all of the tables
foreach ($this->get_tables() as $table)
@@ -226,39 +226,38 @@ class phpbb_database_test_connection_manager
switch ($this->config['dbms'])
{
- case 'mysql':
- case 'mysql4':
- case 'mysqli':
+ case 'phpbb_db_driver_mysql':
+ case 'phpbb_db_driver_mysqli':
$sql = 'SHOW TABLES';
break;
- case 'sqlite':
+ case 'phpbb_db_driver_sqlite':
$sql = 'SELECT name
FROM sqlite_master
WHERE type = "table"';
break;
- case 'mssql':
- case 'mssql_odbc':
- case 'mssqlnative':
+ case 'phpbb_db_driver_mssql':
+ case 'phpbb_db_driver_mssql_odbc':
+ case 'phpbb_db_driver_mssqlnative':
$sql = "SELECT name
FROM sysobjects
WHERE type='U'";
break;
- case 'postgres':
+ case 'phpbb_db_driver_postgres':
$sql = 'SELECT relname
FROM pg_stat_user_tables';
break;
- case 'firebird':
+ case 'phpbb_db_driver_firebird':
$sql = 'SELECT rdb$relation_name
FROM rdb$relations
WHERE rdb$view_source is null
AND rdb$system_flag = 0';
break;
- case 'oracle':
+ case 'phpbb_db_driver_oracle':
$sql = 'SELECT table_name
FROM USER_TABLES';
break;
@@ -293,8 +292,8 @@ class phpbb_database_test_connection_manager
protected function load_schema_from_file($directory)
{
$schema = $this->dbms['SCHEMA'];
-
- if ($this->config['dbms'] == 'mysql')
+
+ if ($this->config['dbms'] == 'phpbb_db_driver_mysql')
{
$sth = $this->pdo->query('SELECT VERSION() AS version');
$row = $sth->fetch(PDO::FETCH_ASSOC);
@@ -313,7 +312,7 @@ class phpbb_database_test_connection_manager
$queries = file_get_contents($filename);
$sql = phpbb_remove_comments($queries);
-
+
$sql = split_sql_file($sql, $this->dbms['DELIM']);
foreach ($sql as $query)
@@ -328,47 +327,47 @@ class phpbb_database_test_connection_manager
protected function get_dbms_data($dbms)
{
$available_dbms = array(
- 'firebird' => array(
+ 'phpbb_db_driver_firebird' => array(
'SCHEMA' => 'firebird',
'DELIM' => ';;',
'PDO' => 'firebird',
),
- 'mysqli' => array(
+ 'phpbb_db_driver_mysqli' => array(
'SCHEMA' => 'mysql_41',
'DELIM' => ';',
'PDO' => 'mysql',
),
- 'mysql' => array(
+ 'phpbb_db_driver_mysql' => array(
'SCHEMA' => 'mysql',
'DELIM' => ';',
'PDO' => 'mysql',
),
- 'mssql' => array(
+ 'phpbb_db_driver_mssql' => array(
'SCHEMA' => 'mssql',
'DELIM' => 'GO',
'PDO' => 'odbc',
),
- 'mssql_odbc'=> array(
+ 'phpbb_db_driver_mssql_odbc'=> array(
'SCHEMA' => 'mssql',
'DELIM' => 'GO',
'PDO' => 'odbc',
),
- 'mssqlnative' => array(
+ 'phpbb_db_driver_mssqlnative' => array(
'SCHEMA' => 'mssql',
'DELIM' => 'GO',
'PDO' => 'sqlsrv',
),
- 'oracle' => array(
+ 'phpbb_db_driver_oracle' => array(
'SCHEMA' => 'oracle',
'DELIM' => '/',
'PDO' => 'oci',
),
- 'postgres' => array(
+ 'phpbb_db_driver_postgres' => array(
'SCHEMA' => 'postgres',
'DELIM' => ';',
'PDO' => 'pgsql',
),
- 'sqlite' => array(
+ 'phpbb_db_driver_sqlite' => array(
'SCHEMA' => 'sqlite',
'DELIM' => ';',
'PDO' => 'sqlite2',
@@ -397,7 +396,7 @@ class phpbb_database_test_connection_manager
switch ($this->config['dbms'])
{
- case 'firebird':
+ case 'phpbb_db_driver_firebird':
$sql = 'SELECT RDB$GENERATOR_NAME
FROM RDB$GENERATORS
WHERE RDB$SYSTEM_FLAG = 0';
@@ -409,7 +408,7 @@ class phpbb_database_test_connection_manager
}
break;
- case 'oracle':
+ case 'phpbb_db_driver_oracle':
$sql = 'SELECT sequence_name
FROM USER_SEQUENCES';
$result = $this->pdo->query($sql);
@@ -444,7 +443,7 @@ class phpbb_database_test_connection_manager
switch ($this->config['dbms'])
{
- case 'oracle':
+ case 'phpbb_db_driver_oracle':
// Get all of the information about the sequences
$sql = "SELECT t.table_name, tc.column_name, d.referenced_name as sequence_name, s.increment_by, s.min_value
FROM USER_TRIGGERS t
@@ -486,7 +485,7 @@ class phpbb_database_test_connection_manager
}
break;
- case 'postgres':
+ case 'phpbb_db_driver_postgres':
// Get the sequences attached to the tables
$sql = 'SELECT column_name, table_name FROM information_schema.columns
WHERE table_name IN (' . implode(', ', $table_names) . ")
diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php
index 16e1ccaff9..59979e035d 100644
--- a/tests/test_framework/phpbb_functional_test_case.php
+++ b/tests/test_framework/phpbb_functional_test_case.php
@@ -34,13 +34,36 @@ class phpbb_functional_test_case extends phpbb_test_case
static protected $config = array();
static protected $already_installed = false;
- public function setUp()
+ static public function setUpBeforeClass()
{
+ parent::setUpBeforeClass();
+
+ self::$config = phpbb_test_case_helpers::get_test_config();
+
+ // Important: this is used both for installation and by
+ // test cases for querying the tables.
+ // Therefore table prefix must be set before a board is
+ // installed, and also before each test case is run.
+ self::$config['table_prefix'] = 'phpbb_';
+
if (!isset(self::$config['phpbb_functional_url']))
{
- $this->markTestSkipped('phpbb_functional_url was not set in test_config and wasn\'t set as PHPBB_FUNCTIONAL_URL environment variable either.');
+ self::markTestSkipped('phpbb_functional_url was not set in test_config and wasn\'t set as PHPBB_FUNCTIONAL_URL environment variable either.');
}
+ if (!self::$already_installed)
+ {
+ self::install_board();
+ self::$already_installed = true;
+ }
+ }
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->bootstrap();
+
$this->cookieJar = new CookieJar;
$this->client = new Goutte\Client(array(), null, $this->cookieJar);
// Reset the curl handle because it is 0 at this point and not a valid
@@ -73,27 +96,16 @@ class phpbb_functional_test_case extends phpbb_test_case
$this->backupStaticAttributesBlacklist += array(
'phpbb_functional_test_case' => array('config', 'already_installed'),
);
-
- if (!static::$already_installed)
- {
- $this->install_board();
- $this->bootstrap();
- static::$already_installed = true;
- }
}
protected function get_db()
{
global $phpbb_root_path, $phpEx;
// so we don't reopen an open connection
- if (!($this->db instanceof dbal))
+ if (!($this->db instanceof phpbb_db_driver))
{
- if (!class_exists('dbal_' . self::$config['dbms']))
- {
- include($phpbb_root_path . 'includes/db/' . self::$config['dbms'] . ".$phpEx");
- }
- $sql_db = 'dbal_' . self::$config['dbms'];
- $this->db = new $sql_db();
+ $dbms = self::$config['dbms'];
+ $this->db = new $dbms();
$this->db->sql_connect(self::$config['dbhost'], self::$config['dbuser'], self::$config['dbpasswd'], self::$config['dbname'], self::$config['dbport']);
}
return $this->db;
@@ -137,19 +149,11 @@ class phpbb_functional_test_case extends phpbb_test_case
return $this->extension_manager;
}
- protected function install_board()
+ static protected function install_board()
{
global $phpbb_root_path, $phpEx;
- self::$config = phpbb_test_case_helpers::get_test_config();
-
- if (!isset(self::$config['phpbb_functional_url']))
- {
- return;
- }
-
- self::$config['table_prefix'] = 'phpbb_';
- $this->recreate_database(self::$config);
+ self::recreate_database(self::$config);
if (file_exists($phpbb_root_path . "config.$phpEx"))
{
@@ -194,19 +198,30 @@ class phpbb_functional_test_case extends phpbb_test_case
));
// end data
- $content = $this->do_request('install');
- $this->assertContains('Welcome to Installation', $content);
-
- $this->do_request('create_table', $data);
-
- $this->do_request('config_file', $data);
+ $content = self::do_request('install');
+ self::assertNotSame(false, $content);
+ self::assertContains('Welcome to Installation', $content);
+
+ // Installer uses 3.0-style dbms name
+ $data['dbms'] = str_replace('phpbb_db_driver_', '', $data['dbms']);
+ $content = self::do_request('create_table', $data);
+ self::assertNotSame(false, $content);
+ self::assertContains('The database tables used by phpBB', $content);
+ // 3.0 or 3.1
+ self::assertContains('have been created and populated with some initial data.', $content);
+
+ $content = self::do_request('config_file', $data);
+ self::assertNotSame(false, $content);
+ self::assertContains('Configuration file', $content);
file_put_contents($phpbb_root_path . "config.$phpEx", phpbb_create_config_file_data($data, self::$config['dbms'], true, true));
- $this->do_request('final', $data);
+ $content = self::do_request('final', $data);
+ self::assertNotSame(false, $content);
+ self::assertContains('You have successfully installed', $content);
copy($phpbb_root_path . "config.$phpEx", $phpbb_root_path . "config_test.$phpEx");
}
- private function do_request($sub, $post_data = null)
+ static private function do_request($sub, $post_data = null)
{
$context = null;
@@ -225,13 +240,62 @@ class phpbb_functional_test_case extends phpbb_test_case
return file_get_contents(self::$config['phpbb_functional_url'] . 'install/index.php?mode=install&sub=' . $sub, false, $context);
}
- private function recreate_database($config)
+ static private function recreate_database($config)
{
$db_conn_mgr = new phpbb_database_test_connection_manager($config);
$db_conn_mgr->recreate_db();
}
- protected function login()
+ /**
+ * Creates a new user with limited permissions
+ *
+ * @param string $username Also doubles up as the user's password
+ * @return int ID of created user
+ */
+ protected function create_user($username)
+ {
+ // Required by unique_id
+ global $config;
+
+ $config = new phpbb_config(array());
+ $config['rand_seed'] = '';
+ $config['rand_seed_last_update'] = time() + 600;
+
+ // Required by user_add
+ global $db, $cache, $phpbb_dispatcher;
+ $db = $this->get_db();
+ if (!function_exists('phpbb_mock_null_cache'))
+ {
+ require_once(__DIR__ . '/../mock/null_cache.php');
+ }
+ $cache = new phpbb_mock_null_cache;
+
+ if (!function_exists('utf_clean_string'))
+ {
+ require_once(__DIR__ . '/../../phpBB/includes/utf/utf_tools.php');
+ }
+ if (!function_exists('user_add'))
+ {
+ require_once(__DIR__ . '/../../phpBB/includes/functions_user.php');
+ }
+ set_config(null, null, null, $config);
+ set_config_count(null, null, null, $config);
+ $phpbb_dispatcher = new phpbb_mock_event_dispatcher();
+
+ $user_row = array(
+ 'username' => $username,
+ 'group_id' => 2,
+ 'user_email' => 'nobody@example.com',
+ 'user_type' => 0,
+ 'user_lang' => 'en',
+ 'user_timezone' => 0,
+ 'user_dateformat' => '',
+ 'user_password' => phpbb_hash($username),
+ );
+ return user_add($user_row);
+ }
+
+ protected function login($username = 'admin')
{
$this->add_lang('ucp');
@@ -239,7 +303,9 @@ class phpbb_functional_test_case extends phpbb_test_case
$this->assertContains($this->lang('LOGIN_EXPLAIN_UCP'), $crawler->filter('html')->text());
$form = $crawler->selectButton($this->lang('LOGIN'))->form();
- $login = $this->client->submit($form, array('username' => 'admin', 'password' => 'admin'));
+ $crawler = $this->client->submit($form, array('username' => $username, 'password' => $username));
+ $this->assert_response_success();
+ $this->assertContains($this->lang('LOGIN_REDIRECT'), $crawler->filter('html')->text());
$cookies = $this->cookieJar->all();
@@ -257,7 +323,7 @@ class phpbb_functional_test_case extends phpbb_test_case
* Login to the ACP
* You must run login() before calling this.
*/
- protected function admin_login()
+ protected function admin_login($username = 'admin')
{
$this->add_lang('acp/common');
@@ -277,7 +343,9 @@ class phpbb_functional_test_case extends phpbb_test_case
{
if (strpos($field, 'password_') === 0)
{
- $login = $this->client->submit($form, array('username' => 'admin', $field => 'admin'));
+ $crawler = $this->client->submit($form, array('username' => $username, $field => $username));
+ $this->assert_response_success();
+ $this->assertContains($this->lang('LOGIN_ADMIN_SUCCESS'), $crawler->filter('html')->text());
$cookies = $this->cookieJar->all();
@@ -358,4 +426,20 @@ class phpbb_functional_test_case extends phpbb_test_case
$content = $this->client->getResponse()->getContent();
$this->assertNotContains('Fatal error:', $content);
}
+
+ public function assert_filter($crawler, $expr, $msg = null)
+ {
+ $nodes = $crawler->filter($expr);
+ if ($msg)
+ {
+ $msg .= "\n";
+ }
+ else
+ {
+ $msg = '';
+ }
+ $msg .= "`$expr` not found in DOM.";
+ $this->assertGreaterThan(0, count($nodes), $msg);
+ return $nodes;
+ }
}
diff --git a/tests/test_framework/phpbb_test_case_helpers.php b/tests/test_framework/phpbb_test_case_helpers.php
index fe15eded90..47459832d5 100644
--- a/tests/test_framework/phpbb_test_case_helpers.php
+++ b/tests/test_framework/phpbb_test_case_helpers.php
@@ -54,7 +54,7 @@ class phpbb_test_case_helpers
if (extension_loaded('sqlite') && version_compare(PHPUnit_Runner_Version::id(), '3.4.15', '>='))
{
$config = array_merge($config, array(
- 'dbms' => 'sqlite',
+ 'dbms' => 'phpbb_db_driver_sqlite',
'dbhost' => dirname(__FILE__) . '/../phpbb_unit_tests.sqlite2', // filename
'dbport' => '',
'dbname' => '',
@@ -78,7 +78,7 @@ class phpbb_test_case_helpers
include($test_config);
$config = array_merge($config, array(
- 'dbms' => $dbms,
+ 'dbms' => phpbb_convert_30_dbms_to_31($dbms),
'dbhost' => $dbhost,
'dbport' => $dbport,
'dbname' => $dbname,
@@ -104,8 +104,13 @@ class phpbb_test_case_helpers
if (isset($_SERVER['PHPBB_TEST_DBMS']))
{
+ if (!function_exists('phpbb_convert_30_dbms_to_31'))
+ {
+ require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php';
+ }
+
$config = array_merge($config, array(
- 'dbms' => isset($_SERVER['PHPBB_TEST_DBMS']) ? $_SERVER['PHPBB_TEST_DBMS'] : '',
+ 'dbms' => isset($_SERVER['PHPBB_TEST_DBMS']) ? phpbb_convert_30_dbms_to_31($_SERVER['PHPBB_TEST_DBMS']) : '',
'dbhost' => isset($_SERVER['PHPBB_TEST_DBHOST']) ? $_SERVER['PHPBB_TEST_DBHOST'] : '',
'dbport' => isset($_SERVER['PHPBB_TEST_DBPORT']) ? $_SERVER['PHPBB_TEST_DBPORT'] : '',
'dbname' => isset($_SERVER['PHPBB_TEST_DBNAME']) ? $_SERVER['PHPBB_TEST_DBNAME'] : '',
diff --git a/tests/upload/filespec_test.php b/tests/upload/filespec_test.php
index c7ff2e78e0..87cd00197f 100644
--- a/tests/upload/filespec_test.php
+++ b/tests/upload/filespec_test.php
@@ -205,8 +205,7 @@ class phpbb_filespec_test extends phpbb_test_case
*/
public function test_get_extension($filename, $expected)
{
- $filespec = $this->get_filespec();
- $this->assertEquals($expected, $filespec->get_extension($filename));
+ $this->assertEquals($expected, filespec::get_extension($filename));
}
public function is_image_variables()
diff --git a/travis/phpunit-mysql-travis.xml b/travis/phpunit-mysql-travis.xml
index 5366494c8b..cc033ff959 100644
--- a/travis/phpunit-mysql-travis.xml
+++ b/travis/phpunit-mysql-travis.xml
@@ -28,7 +28,7 @@
</groups>
<php>
- <server name="PHPBB_TEST_DBMS" value="mysqli" />
+ <server name="PHPBB_TEST_DBMS" value="phpbb_db_driver_mysqli" />
<server name="PHPBB_TEST_DBHOST" value="0.0.0.0" />
<server name="PHPBB_TEST_DBPORT" value="3306" />
<server name="PHPBB_TEST_DBNAME" value="phpbb_tests" />
diff --git a/travis/phpunit-postgres-travis.xml b/travis/phpunit-postgres-travis.xml
index 0383edd9d6..5019c225b4 100644
--- a/travis/phpunit-postgres-travis.xml
+++ b/travis/phpunit-postgres-travis.xml
@@ -30,7 +30,7 @@
<php>
<!-- "Real" test database -->
<!-- uncomment, otherwise sqlite memory runs -->
- <server name="PHPBB_TEST_DBMS" value="postgres"/>
+ <server name="PHPBB_TEST_DBMS" value="phpbb_db_driver_postgres"/>
<server name="PHPBB_TEST_DBHOST" value="localhost" />
<server name="PHPBB_TEST_DBPORT" value="5432" />
<server name="PHPBB_TEST_DBNAME" value="phpbb_tests" />